├── src ├── ax5menu.gif ├── ax5menu.scss ├── scss │ ├── _ax5menu_variables.scss │ └── _ax5menu.scss ├── modules │ └── ax5menu-tmpl.js └── ax5menu.js ├── .travis.yml ├── deploy.sh ├── test ├── bower.json ├── spec.md ├── test.menu.html ├── test.menu.js ├── index2.html └── index.html ├── bower.json ├── package.json ├── LICENSE ├── karma.conf.js ├── README.md ├── API.md └── dist ├── ax5menu.min.js ├── ax5menu.js └── ax5menu.min.js.map /src/ax5menu.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ax5ui/ax5ui-menu/HEAD/src/ax5menu.gif -------------------------------------------------------------------------------- /src/ax5menu.scss: -------------------------------------------------------------------------------- 1 | @import "node_modules/ax5core/src/_ax5.scss"; 2 | 3 | @import "scss/ax5menu_variables"; 4 | @import "scss/ax5menu"; -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | before_script: 5 | - export CHROME_BIN=chromium-browser 6 | - export DISPLAY=:99.0 7 | - sh -e /etc/init.d/xvfb start -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | VERSION=`jq '.version' package.json | sed -e 's/^"//' -e 's/"$//'` 2 | 3 | git tag -a $VERSION -m $VERSION || true 4 | 5 | git push origin HEAD:master --follow-tags --force || true 6 | 7 | npm publish || true 8 | -------------------------------------------------------------------------------- /test/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ax5ui-formatter-tester", 3 | "dependencies": { 4 | "jquery": "^1.11.0", 5 | "ax5core": ">=1.4.115", 6 | "ax5ui-toast": ">=1.3.0", 7 | "bootstrap": "^3.3.6", 8 | "font-awesome": "" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/spec.md: -------------------------------------------------------------------------------- 1 | # menu 2 | 너비, 포지션, 화살표, 박스스타일 3 | 4 | ## 메뉴의 표현 유형 5 | - 메뉴바 형태 6 | - top / bottom 7 | - left / right 8 | - standAlone 타입 9 | - 포지션 (absolute/relative) 10 | 11 | ### 아이템 컨테이너 12 | - stack 13 | - table 14 | 15 | ## 구성 16 | - 아이템 17 | - 구분선 18 | - DIV 19 | 20 | ### 아이템 21 | - 아이템 그룹 22 | - 애드온 23 | - 썹네일 24 | - 단축키 25 | - arrow 26 | - 라벨 27 | - 링크 28 | 29 | ### 아이템 상태 30 | - 일반 31 | - disable 32 | - theme status(basic, info, danger, warning ..) -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ax5ui-menu", 3 | "version": "1.4.131", 4 | "description": "A menu plugin that works with Bootstrap & jQuery", 5 | "authors": [ 6 | "ThomasJ " 7 | ], 8 | "main": "dist/ax5menu.js", 9 | "keywords": [ 10 | "bootstrap", 11 | "jquery", 12 | "ax5ui", 13 | "plugin", 14 | "bootstrap jQuery plugins", 15 | "formatter", 16 | "ax5ui-menu", 17 | "javascript ui" 18 | ], 19 | "dependencies": { 20 | "jquery": "", 21 | "ax5core": ">=1.4.115" 22 | }, 23 | "license": "MIT", 24 | "homepage": "ax5.io" 25 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ax5ui-menu", 3 | "version": "1.4.131", 4 | "description": "A menu plugin that works with Bootstrap & jQuery", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/ax5ui/ax5ui-menu" 9 | }, 10 | "author": { 11 | "name": "Thomas Jang", 12 | "email": "tom@axisj.com", 13 | "url": "ax5.io" 14 | }, 15 | "keywords": [ 16 | "bootstrap", 17 | "jquery", 18 | "ax5ui", 19 | "plugin", 20 | "bootstrap jQuery plugins", 21 | "menu", 22 | "ax5ui-menu", 23 | "javascript ui" 24 | ], 25 | "scripts": { 26 | "test": "karma start karma.conf.js" 27 | }, 28 | "dependencies": { 29 | "jquery": "", 30 | "ax5core": ">=1.4.115" 31 | }, 32 | "devDependencies": { 33 | "karma": "^1.3.0", 34 | "karma-mocha": "^1.2.0", 35 | "karma-phantomjs-launcher": "^1.0.2", 36 | "mocha": "^3.1.0", 37 | "phantomjs-prebuilt": "^2.1.13", 38 | "webdriverio": "^4.6.1" 39 | } 40 | } -------------------------------------------------------------------------------- /test/test.menu.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | ax5ui testing 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2016 Thomas Jang, Brant and Team AXISJ 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /test/test.menu.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. tom@axisj.com 3 | * - github.com/thomasjang 4 | * - www.axisj.com 5 | */ 6 | 7 | describe('ax5menu TEST', function () { 8 | var myUI; 9 | 10 | var tmpl = ''; 11 | 12 | $(document.body).append(tmpl); 13 | 14 | 15 | /// 16 | it('new ax5menu', function (done) { 17 | try { 18 | myUI = new ax5.ui.menu({ 19 | position: "absolute", // default position is "fixed" 20 | icons: { 21 | 'arrow': '▸' 22 | }, 23 | items: [ 24 | { 25 | label: "Menu A", 26 | items: [ 27 | {label: "Menu A-0"}, 28 | {label: "Menu A-1"}, 29 | {label: "Menu A-2"} 30 | ] 31 | }, 32 | { 33 | label: "Menu B", 34 | items: [ 35 | {label: "Menu B-0"}, 36 | {label: "Menu B-1"}, 37 | {label: "Menu B-2"} 38 | ] 39 | } 40 | ] 41 | }); 42 | 43 | done(); 44 | } catch (e) { 45 | done(e); 46 | } 47 | }); 48 | 49 | 50 | it('popup menu', function (done) { 51 | myUI.popup({top: 0, left: 0}); 52 | done(jQuery(".ax5-ui-menu").get(0) ? "" : "error popup"); 53 | }); 54 | 55 | 56 | it('close menu', function (done) { 57 | myUI.close(); 58 | done(jQuery(".ax5-ui-menu").get(0) ? "error close" : ""); 59 | }); 60 | 61 | }); -------------------------------------------------------------------------------- /test/index2.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | Title 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017. tom@axisj.com 3 | * - github.com/thomasjang 4 | * - www.axisj.com 5 | */ 6 | 7 | // Karma configuration 8 | // Generated on Wed Sep 21 2016 00:37:04 GMT+0900 (KST) 9 | 10 | module.exports = function (config) { 11 | var configuration = { 12 | // base path that will be used to resolve all patterns (eg. files, exclude) 13 | basePath: '', 14 | 15 | // frameworks to use 16 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 17 | frameworks: ['mocha'], 18 | 19 | // list of files / patterns to load in the browser 20 | files: [ 21 | 'https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js', 22 | 'https://cdnjs.cloudflare.com/ajax/libs/should.js/11.1.2/should.min.js', 23 | 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js', 24 | 'https://cdn.rawgit.com/ax5ui/ax5core/master/dist/ax5core.min.js', 25 | 'dist/ax5menu.min.js', 26 | 'test/test.*.js' 27 | ], 28 | // list of files to exclude 29 | exclude: [], 30 | // preprocess matching files before serving them to the browser 31 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 32 | preprocessors: {}, 33 | // test results reporter to use 34 | // possible values: 'dots', 'progress' 35 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 36 | reporters: ['progress'], 37 | // web server port 38 | port: 9876, 39 | // enable / disable colors in the output (reporters and logs) 40 | colors: true, 41 | // level of logging 42 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 43 | logLevel: config.LOG_INFO, 44 | // enable / disable watching file and executing tests whenever any file changes 45 | autoWatch: true, 46 | // start these browsers 47 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 48 | //browsers: ['PhantomJS'], 49 | browsers: ['Chrome', 'Firefox'], 50 | customLaunchers: { 51 | Chrome_travis_ci: { 52 | base: 'Chrome', 53 | flags: ['--no-sandbox'] 54 | } 55 | }, 56 | singleRun: true, 57 | concurrency: Infinity 58 | }; 59 | 60 | if (process.env.TRAVIS) { 61 | configuration.browsers = ['PhantomJS']; 62 | } 63 | 64 | config.set(configuration); 65 | } 66 | -------------------------------------------------------------------------------- /src/scss/_ax5menu_variables.scss: -------------------------------------------------------------------------------- 1 | //============== ax5menu 2 | $ax5menu-z-index: 2000 !default; 3 | $ax5menu-bg: #eee !default; 4 | $ax5menu-inner-border: 1px solid !default; 5 | $ax5menu-inner-border-color: #aaa !default; 6 | 7 | $ax5menu-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2) !default; 8 | $ax5menu-border-radius: 5px !default; 9 | 10 | $ax5menu-body-padding: 5px 0px !default; 11 | $ax5menu-heading-padding: $panel-heading-padding !default; 12 | $ax5menu-buttons-padding: 10px 0px 5px 0px !default; 13 | 14 | $ax5menu-content-border: 0px solid !default; 15 | $ax5menu-content-border-color: none !default; 16 | $ax5menu-content-border-radius: 0px !default; 17 | $ax5menu-content-padding: 0px !default; 18 | 19 | $ax5menu-easing-time-open: 0.3s !default; 20 | $ax5menu-easing-time-close: 0.2s !default; 21 | $ax5menu-arrow-size: 10px !default; 22 | $ax5menu-arrow-border-width: 1px !default; 23 | 24 | $ax5menu-item-padding: 4px 0px !default; 25 | $ax5menu-item-html-padding: 0px 5px !default; 26 | $ax5menu-item-font-size: 13px !default; 27 | $ax5menu-item-checkbox-width: 18px !default; 28 | $ax5menu-item-handle-width: 14px !default; 29 | 30 | $ax5menubar-item-padding: 0px 10px !default; 31 | 32 | //** Border color for elements within dialog 33 | $ax5menu-default-text: $panel-default-text !default; 34 | $ax5menu-default-border-color: $ax5menu-inner-border-color !default; 35 | $ax5menu-default-heading-bg: $panel-default-heading-bg !default; 36 | $ax5menu-default-item-bg: #eee !default; 37 | $ax5menu-default-item-text: #444 !default; 38 | $ax5menu-default-item-hover-bg: #999 !default; 39 | $ax5menu-default-item-hover-text: #fff !default; 40 | 41 | 42 | $ax5menu-primary-text: $panel-primary-text !default; 43 | $ax5menu-primary-border-color: $brand-primary !default; 44 | $ax5menu-primary-heading-bg: $panel-primary-heading-bg !default; 45 | $ax5menu-primary-item-bg: #eee !default; 46 | $ax5menu-primary-item-text: $brand-primary !default; 47 | $ax5menu-primary-item-hover-bg: $brand-primary !default; 48 | $ax5menu-primary-item-hover-text: #fff !default; 49 | 50 | $ax5menu-success-text: $panel-success-text !default; 51 | $ax5menu-success-border-color: $brand-success !default; 52 | $ax5menu-success-heading-bg: $panel-success-heading-bg !default; 53 | $ax5menu-success-item-bg: #eee !default; 54 | $ax5menu-success-item-text: $brand-success !default; 55 | $ax5menu-success-item-hover-bg: $brand-success !default; 56 | $ax5menu-success-item-hover-text: #fff !default; 57 | 58 | $ax5menu-info-text: $panel-info-text !default; 59 | $ax5menu-info-border-color: $brand-info !default; 60 | $ax5menu-info-heading-bg: $panel-info-heading-bg !default; 61 | $ax5menu-info-item-bg: #eee !default; 62 | $ax5menu-info-item-text: darken($brand-info, 10%) !default; 63 | $ax5menu-info-item-hover-bg: $brand-info !default; 64 | $ax5menu-info-item-hover-text: #fff !default; 65 | 66 | $ax5menu-warning-text: $panel-warning-text !default; 67 | $ax5menu-warning-border-color: $brand-warning !default; 68 | $ax5menu-warning-heading-bg: $panel-warning-heading-bg !default; 69 | $ax5menu-warning-item-bg: #eee !default; 70 | $ax5menu-warning-item-text: darken($brand-warning, 20%) !default; 71 | $ax5menu-warning-item-hover-bg: darken($brand-warning, 0%) !default; 72 | $ax5menu-warning-item-hover-text: #fff !default; 73 | 74 | $ax5menu-danger-text: $panel-danger-text !default; 75 | $ax5menu-danger-border-color: $brand-danger !default; 76 | $ax5menu-danger-heading-bg: $panel-danger-heading-bg !default; 77 | $ax5menu-danger-item-bg: #eee !default; 78 | $ax5menu-danger-item-text: darken($brand-danger, 20%) !default; 79 | $ax5menu-danger-item-hover-bg: darken($brand-danger, 0%) !default; 80 | $ax5menu-danger-item-hover-text: #fff !default; 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/ax5ui/ax5ui-menu.svg?branch=master)](https://travis-ci.org/ax5ui/ax5ui-menu) 2 | [![npm version](https://badge.fury.io/js/ax5ui-menu.svg)](https://badge.fury.io/js/ax5ui-menu) 3 | [![](https://img.shields.io/npm/dm/ax5ui-menu.svg)](https://www.npmjs.com/package/ax5ui-menu) 4 | 5 | 6 | # ax5ui-menu 7 | "menu" displays a list of items in a hierarchical structure. 8 | 9 | ![ax5menu](src/ax5menu.gif) 10 | 11 | > *Dependencies* 12 | > * _[jQuery 1.X+](http://jquery.com/)_ 13 | > * _[ax5core](http://ax5.io/ax5core)_ 14 | > * _[bootstrap](http://getbootstrap.com/)_ 15 | 16 | 17 | ### Install with bower 18 | ```sh 19 | bower install ax5ui-menu 20 | ``` 21 | [bower](http://bower.io/#install-bower) is web front-end package manager. 22 | [bower](http://bower.io/#install-bower) is web front-end package manager. 23 | When you install `bower`, it will be installed under the `bower_components` folder to resolve the plug-in dependencies. 24 | (You can change the folder location. [.bowerrc](http://bower.io/docs/config/#bowerrc-specification) ) 25 | 26 | It is recommended that you install by using `bower`. 27 | If you've never used bower, please refer to [http://bower.io/#install-bower](http://bower.io/#install-bower). 28 | 29 | ### Install with npm 30 | If you do not use bower, it also can be installed by using npm as an alternative. 31 | In case of npm, which is the package manager for the front end, you need to solve the problem of plug-in dependencies. 32 | 33 | ```sh 34 | npm install jquery 35 | npm install ax5core 36 | npm install ax5ui-menu 37 | ``` 38 | 39 | After downloading the install file of npm, you will need to copy it to the location where you want to use as a resource for the project. 40 | If the copy process is inconvenient, it also can be done easily by using `gulp` or `grunt`. 41 | 42 | ### Download code 43 | - [ax5core Github releases](https://github.com/ax5ui/ax5core/releases) 44 | - [ax5ui-menu Github releases](https://github.com/ax5ui/ax5ui-menu/releases) 45 | 46 | 47 | ### Insert "ax5menu" in HTML HEAD. 48 | Folder location can be any for your project. However, please be sure to assign the right path in the project. 49 | 50 | ```html 51 | 52 | 53 | 54 | 55 | ``` 56 | 57 | **CDN urls** 58 | This is a list of CDN urls for ax5ui-menu. ax5ui offers the CDN services through rawgit. 59 | ``` 60 | https://cdn.rawgit.com/ax5ui/ax5ui-menu/master/dist/ax5menu.css 61 | https://cdn.rawgit.com/ax5ui/ax5ui-menu/master/dist/ax5menu.js 62 | https://cdn.rawgit.com/ax5ui/ax5ui-menu/master/dist/ax5menu.min.js 63 | ``` 64 | 65 | ### Basic Usage 66 | ```js 67 | var menu = new ax5.ui.menu({ 68 | theme: 'primary', 69 | items: [ 70 | {label: "Menu 0"}, 71 | {label: "Menu 1"} 72 | ] 73 | }); 74 | 75 | $(document).bind("contextmenu", function (e) { 76 | menu.popup(e); // e || {left: 'Number', top: 'Number', direction: '', width: 'Number'} 77 | ax5.util.stopEvent(e); 78 | }); 79 | ``` 80 | 81 | *** 82 | 83 | ### Preview 84 | - [See Demonstration](http://ax5.io/ax5ui-menu/demo/index.html) 85 | 86 | If you have any questions, please refer to the following [gitHub](https://github.com/ax5ui/ax5ui-kernel) 87 | 88 | ## Question 89 | - https://jsdev.kr/c/axisj/ax5ui 90 | - https://github.com/ax5ui/ax5ui-kernel/issues 91 | 92 | [![axisj-contributed](https://img.shields.io/badge/AXISJ.com-Contributed-green.svg)](https://github.com/axisj) 93 | ![](https://img.shields.io/badge/Seowoo-Mondo&Thomas-red.svg) -------------------------------------------------------------------------------- /src/modules/ax5menu-tmpl.js: -------------------------------------------------------------------------------- 1 | // ax5.ui.menu.tmpl 2 | (function () { 3 | var MENU = ax5.ui.menu; 4 | 5 | var tmpl = function (columnKeys) { 6 | return ` 7 |
8 |
9 | {{#${columnKeys.items}}} 10 | {{^@isMenu}} 11 | {{#divide}} 12 |
13 | {{/divide}} 14 | {{#html}} 15 |
{{{@html}}}
16 | {{/html}} 17 | {{/@isMenu}} 18 | {{#@isMenu}} 19 |
20 | 21 | {{#check}} 22 | 23 | {{/check}} 24 | {{^check}} 25 | 26 | {{/check}} 27 | 28 | {{#icon}} 29 | {{{.}}} 30 | {{/icon}} 31 | {{{${columnKeys.label}}}} 32 | {{#accelerator}} 33 | {{.}} 34 | {{/accelerator}} 35 | {{#@hasChild}} 36 | {{{cfg.icons.arrow}}} 37 | {{/@hasChild}} 38 |
39 | {{/@isMenu}} 40 | 41 | {{/${columnKeys.items}}} 42 |
43 |
44 |
45 | `; 46 | }; 47 | var tmplMenubar = function (columnKeys) { 48 | return ` 49 |
50 |
51 | {{#${columnKeys.items}}} 52 | {{^@isMenu}} 53 | {{#divide}} 54 |
55 | {{/divide}} 56 | {{#html}} 57 |
{{{@html}}}
58 | {{/html}} 59 | {{/@isMenu}} 60 | {{#@isMenu}} 61 |
62 | {{#icon}} 63 | {{{.}}} 64 | {{/icon}} 65 | {{{${columnKeys.label}}}} 66 |
67 | {{/@isMenu}} 68 | {{/${columnKeys.items}}} 69 |
70 |
71 | `; 72 | }; 73 | 74 | MENU.tmpl = { 75 | "tmpl" : tmpl, 76 | "tmplMenubar" : tmplMenubar, 77 | 78 | get: function (tmplName, data, columnKeys) { 79 | return ax5.mustache.render(MENU.tmpl[tmplName].call(this, columnKeys), data); 80 | } 81 | }; 82 | })(); -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # Menu 2 | > It is a menu UI. You can use the kind of context menu and the menu bar. 3 | 4 | How to initialize the properties of the UI, there are two. 5 | You can pass the property values of the UI to `setConfig`. 6 | Using the `new` you can pass when you initialize the UI. 7 | 8 | ## setConfig 9 | `setConfig([options, callInit=true])` 10 | 11 | ```js 12 | var menu = new ax5.ui.menu(); 13 | menu.setConfig({ 14 | theme: 'default', 15 | //width: 200, 16 | offset: {left: 0, top: 0}, 17 | position: "absolute", 18 | icons: { 19 | 'arrow': '' 20 | }, 21 | items: [ 22 | {label:"label"}, 23 | {divide: true}, 24 | {label:"label", items: [ 25 | {label:"label"} 26 | ]} 27 | ] 28 | }); 29 | ``` 30 | **Easy Way - without setConfig** 31 | ```js 32 | var menu = new ax5.ui.menu({ 33 | theme: 'default', 34 | //width: 200, 35 | position: "absolute", 36 | icons: { 37 | 'arrow': '' 38 | }, 39 | items: [ 40 | {label:"label"}, 41 | {divide: true}, 42 | {label:"label", items: [ 43 | {label:"label"} 44 | ]} 45 | ] 46 | }); 47 | ``` 48 | 49 | ### theme 50 | Type: `String` 51 | default, primary, info, warning, danger 52 | 53 | ### position 54 | Type: `String` 55 | abslute, fixed 56 | 57 | ### width 58 | Type: `Number` 59 | 60 | 61 | ### iconWidth 62 | Type: `Number` 63 | 64 | ### acceleratorWidth 65 | Type: `Number` 66 | 67 | ### offset 68 | Type: `Object` 69 | 70 | ```js 71 | {left: 10, top: 10} 72 | ``` 73 | 74 | ### itemClickAndClose 75 | Type: `Boolean` 76 | When you click on an item on the menu, whether to close the menu 77 | 78 | ### icons 79 | Type: `Object` 80 | 81 | ```js 82 | {'arrow': ''} 83 | ``` 84 | 85 | ### items 86 | Type: `Array` 87 | 88 | ### onStateChanged 89 | Type: `Function` 90 | 91 | ```js 92 | var menu = new ax5.ui.menu({ 93 | items: [ 94 | {label:"label"} 95 | ], 96 | onStateChanged: function(){ 97 | console.log(this); 98 | } 99 | }); 100 | ``` 101 | or 102 | ```js 103 | var menu = new ax5.ui.menu({}); 104 | menu.onStateChanged = function(){ 105 | console.log(this); 106 | }; 107 | ``` 108 | 109 | ### onClick 110 | Type: `Function` 111 | ```js 112 | var menu = new ax5.ui.menu({ 113 | items: [ 114 | {label:"label"} 115 | ], 116 | onClick: function(){ 117 | console.log(this); 118 | } 119 | }); 120 | ``` 121 | or 122 | ```js 123 | var menu = new ax5.ui.menu({}); 124 | menu.onClick = function(){ 125 | console.log(this); 126 | }; 127 | ``` 128 | 129 | ### onLoad 130 | Type: `Function` 131 | ```js 132 | var menu = new ax5.ui.menu({ 133 | items: [ 134 | {label:"label"} 135 | ], 136 | onLoad: function(){ 137 | console.log(this); 138 | } 139 | }); 140 | ``` 141 | or 142 | ```js 143 | var menu = new ax5.ui.menu({}); 144 | menu.onLoad = function(){ 145 | console.log(this); 146 | }; 147 | ``` 148 | 149 | 150 | - - - 151 | 152 | ## popup 153 | `popup(event|position, opts)` 154 | ```js 155 | $(document).bind("contextmenu", function (e) { 156 | menu.popup(e); 157 | ax5.util.stopEvent(e); 158 | }); 159 | ``` 160 | or use custom position 161 | ```js 162 | menu.popup({left:0, top:0, width:200}); 163 | $(document).bind("contextmenu", function (e) { 164 | menu.popup(e, {theme:"basic", filter:function(){ 165 | return true; 166 | }}); 167 | ax5.util.stopEvent(e); 168 | }); 169 | ``` 170 | 171 | - - - 172 | ## close 173 | `close()` 174 | 175 | - - - 176 | 177 | ## getCheckValue 178 | `getCheckValue()` 179 | ```js 180 | menu.getCheckValue(); 181 | // {} 182 | ``` 183 | 184 | - - - 185 | 186 | ## attach 187 | `attach(Element)` 188 | ```html 189 |
191 | ``` 192 | 193 | ```js 194 | var attachedMenu = new ax5.ui.menu({}); 195 | attachedMenu.attach($("#attachedMenu-target")); 196 | ``` -------------------------------------------------------------------------------- /dist/ax5menu.min.js: -------------------------------------------------------------------------------- 1 | "use strict";!function(){var e,t=ax5.ui,n=ax5.util;t.addClass({className:"menu"},function(){return function(){var i=this,l=void 0;this.instanceId=ax5.getGuid(),this.config={theme:"default",iconWidth:22,acceleratorWidth:100,menuBodyPadding:5,offset:{left:0,top:0},position:"fixed",animateTime:250,items:[],itemClickAndClose:!0,columnKeys:{label:"label",items:"items"}},this.openTimer=null,this.closeTimer=null,this.queue=[],this.menuBar={},this.state=void 0,l=this.config;var a=function(e,t){e?(jQuery(document.body).off("click.ax5menu-"+this.instanceId).on("click.ax5menu-"+this.instanceId,o.bind(this,t)),jQuery(window).off("keydown.ax5menu-"+this.instanceId).on("keydown.ax5menu-"+this.instanceId,function(e){e.which==ax5.info.eventKeys.ESC&&i.close()}),jQuery(window).on("resize.ax5menu-"+this.instanceId).on("resize.ax5menu-"+this.instanceId,function(e){i.close()})):(jQuery(document.body).off("click.ax5menu-"+this.instanceId),jQuery(window).off("keydown.ax5menu-"+this.instanceId),jQuery(window).off("resize.ax5menu-"+this.instanceId))},u=function(e,t){return e&&e.onStateChanged?e.onStateChanged.call(t,t):this.onStateChanged&&this.onStateChanged.call(t,t),i.state=t.state,e=null,t=null,!0},s=function(e){return this.onLoad&&this.onLoad.call(e,e),e=null,!0},c=function t(n,a,c,o){var d=n,r=void 0,h=void 0;return d.theme=n.theme||l.theme,d.cfg={icons:jQuery.extend({},l.icons),iconWidth:n.iconWidth||l.iconWidth,acceleratorWidth:n.acceleratorWidth||l.acceleratorWidth},a.forEach(function(e){e.html||e.divide?(e["@isMenu"]=!1,e.html&&(e["@html"]=e.html.call({item:e,config:l,opt:n}))):e["@isMenu"]=!0}),d[l.columnKeys.items]=a,d["@depth"]=c,d["@path"]=o||"root",d["@hasChild"]=function(){return this[l.columnKeys.items]&&this[l.columnKeys.items].length>0},r=jQuery(e.tmpl.get.call(this,"tmpl",d,l.columnKeys)),jQuery(document.body).append(r),h=this.queue.splice(c),h.forEach(function(e){e.$target.remove()}),this.queue.push({$target:r,data:jQuery.extend({},d)}),r.find("[data-menu-item-index]").bind("mouseover",function(){var e=this.getAttribute("data-menu-item-depth"),a=this.getAttribute("data-menu-item-index"),u=this.getAttribute("data-menu-item-path"),s=void 0,c=void 0,o=void 0,m=void 0,d=void 0,r=void 0;null!=e&&"undefined"!=typeof e&&(d=i.queue[e].data[l.columnKeys.items][a][l.columnKeys.items],r=i.queue[e].$target,r.find("[data-menu-item-index]").removeClass("hover"),jQuery(this).addClass("hover"),r.attr("data-selected-menu-item-index")!=a&&(r.attr("data-selected-menu-item-index",a),d&&d.length>0?(s=jQuery(this),c=s.offset(),o="fixed"==l.position?jQuery(document).scrollTop():0,m={"@parent":{left:c.left,top:c.top,width:s.outerWidth(),height:s.outerHeight()},left:c.left+s.outerWidth()-l.menuBodyPadding,top:c.top-l.menuBodyPadding-1-o},m=jQuery.extend(!0,n,m),t.call(i,m,d,Number(e)+1,u)):i.queue.splice(Number(e)+1).forEach(function(e){e.$target.remove()}))),e=null,a=null,u=null,s=null,c=null,o=null,m=null,d=null,r=null}),r.find("[data-menu-item-index]").bind("mouseout",function(){var e=this.getAttribute("data-menu-item-depth"),t=this.getAttribute("data-menu-item-index"),n=this.getAttribute("data-menu-item-path"),a=void 0;n&&(a=i.queue[e].data[l.columnKeys.items][t][l.columnKeys.items]),a&&a.length>0||jQuery(this).removeClass("hover")}),0==c&&(d.direction&&r.addClass("direction-"+d.direction),u.call(this,null,{self:this,items:a,parent:function(e){if(!e)return!1;var t=null;try{t=Function("","return this.config.items["+e.substring(5).replace(/\./g,"].items[")+"];").call(i)}catch(e){}return t}(d["@path"]),state:"popup"})),m.call(this,r,d),s.call(this,{self:this,items:a,element:r.get(0)}),d=null,r=null,h=null,n=null,a=null,c=null,o=null,this},o=function(e,t){var a=void 0,u=void 0;if(a=n.findParentNode(t.target,function(e){if(e.getAttribute("data-menu-item-index"))return!0})){if("undefined"==typeof e&&(e={}),u=function(t){if(!t)return!1;var n=void 0;try{n=Function("","return this["+t.substring(5).replace(/\./g,"]."+l.columnKeys.items+"[")+"];").call(e.items||l.items)}catch(e){console.log(ax5.info.getError("ax5menu","501","menuItemClick"))}try{return n}finally{n=null}}(a.getAttribute("data-menu-item-path")),!u)return this;u.check&&(function(e){var t={checkbox:function(e){this.checked=!e},radio:function(t){var n=this.name;e.forEach(function(e){e.check&&"radio"===e.check.type&&e.check.name==n&&(e.check.checked=!1)}),this.checked=!t}};t[this.type]&&t[this.type].call(this,this.checked),t=null}.call(u.check,l.items),l.itemClickAndClose||i.queue.forEach(function(e){e.$target.find("[data-menu-item-index]").each(function(){var t=e.data[l.columnKeys.items][this.getAttribute("data-menu-item-index")];t.check&&jQuery(this).find(".item-checkbox-wrap").attr("data-item-checked",t.check.checked)})})),i.onClick&&i.onClick.call(u,u,e.param)&&i.close(),u[l.columnKeys.items]&&0!=u[l.columnKeys.items].length||!l.itemClickAndClose||i.close()}else i.close();return a=null,u=null,this},m=function(e,t){var n=jQuery(window),i=jQuery(document),a="fixed"==l.position?n.height():i.height(),u=n.width(),s=e.outerHeight(),c=e.outerWidth(),o=t.left,m=t.top,d=l.position||"fixed";return o+c>u&&(o=t["@parent"]?t["@parent"].left-c+l.menuBodyPadding:u-c),m+s>a&&(m=a-s),e.css({left:o,top:m,position:d}),e=null,t=null,n=null,i=null,a=null,u=null,s=null,c=null,o=null,m=null,d=null,this};this.init=function(){this.onStateChanged=l.onStateChanged,this.onClick=l.onClick,this.onLoad=l.onLoad,u.call(this,null,{self:this,state:"init"})},this.popup=function(){var e={event:function(e,t){e={left:e.clientX,top:"fixed"==l.position?e.clientY:e.pageY,width:l.width,theme:l.theme},e.left-=5,e.top-=5,l.offset&&(l.offset.left&&(e.left+=l.offset.left),l.offset.top&&(e.top+=l.offset.top)),t=jQuery.extend(!0,e,t);try{return t}finally{e=null}},object:function(e,t){e={left:e.left,top:e.top,width:e.width||l.width,theme:e.theme||l.theme},l.offset&&(l.offset.left&&(e.left+=l.offset.left),l.offset.top&&(e.top+=l.offset.top)),t=jQuery.extend(!0,e,t);try{return t}finally{e=null}}},t=function(e){e&&(l.theme=e)};return function(n,i){if(!n)return this;i=e["undefined"==typeof n.clientX?"object":"event"].call(this,n,i),t(i.theme);var u=[].concat(l.items),s=void 0;return i.items=u,i.filter&&(s=function(e){var t=[];return e.forEach(function(e){e.items&&e.items.length>0&&(e.items=s(e.items)),i.filter.call(e)&&t.push(e)}),t},i.items=u=s(u)),u.length&&(a.call(this,!1),c.call(this,i,u,0),this.popupEventAttachTimer&&clearTimeout(this.popupEventAttachTimer),this.popupEventAttachTimer=setTimeout(function(){a.call(this,!0,i)}.bind(this),500)),n=null,this}}(),this.attach=function(){var t={object:function(e,t){e={left:e.left,top:e.top,width:e.width||l.width,theme:e.theme||l.theme,direction:e.direction||l.direction},t=jQuery.extend(!0,t,e);try{return t}finally{e=null,t=null}}},u=function(e,n,u){var s=jQuery(e),o=s.offset(),m=s.outerHeight(),d=Number(e.getAttribute("data-menu-item-index")),r="fixed"==l.position?jQuery(document).scrollTop():0;if(l.items&&l.items[d][l.columnKeys.items]&&l.items[d][l.columnKeys.items].length){if(i.menuBar.openedIndex==d)return"click"==u&&i.close(),!1;i.menuBar.target.find("[data-menu-item-index]").removeClass("hover"),i.menuBar.opened=!0,i.menuBar.openedIndex=d,s.attr("data-menu-item-opened","true"),s.addClass("hover"),l.offset&&(l.offset.left&&(o.left+=l.offset.left),l.offset.top&&(o.top+=l.offset.top)),n=t.object.call(this,{left:o.left,top:o.top+m-r},n),c.call(i,n,l.items[d][l.columnKeys.items],0,"root."+e.getAttribute("data-menu-item-index")),a.call(i,!0,{})}e=null,n=null,s=null,o=null,m=null,d=null,r=null},s=function(e,t,n){var a=jQuery(e),u=(a.offset(),a.outerHeight(),Number(e.getAttribute("data-menu-item-index")));"fixed"==l.position?jQuery(document).scrollTop():0;!l.items||l.items[u][l.columnKeys.items]&&0!=l.items[u][l.columnKeys.items].length||i.onClick&&i.onClick.call(l.items[u],l.items[u])};return function(t,a){var c,o={},m=l.items;return"undefined"==typeof a&&(a={}),o.theme=a.theme||l.theme,o.cfg={icons:jQuery.extend({},l.icons),iconWidth:a.iconWidth||l.iconWidth,acceleratorWidth:a.acceleratorWidth||l.acceleratorWidth},m.forEach(function(e){e.html||e.divide?(e["@isMenu"]=!1,e.html&&(e["@html"]=e.html.call({item:e,config:l,opt:a}))):e["@isMenu"]=!0}),o[l.columnKeys.items]=m,c=jQuery(e.tmpl.get.call(this,"tmplMenubar",o,l.columnKeys)),i.menuBar={target:jQuery(t),opened:!1},i.menuBar.target.html(c),i.menuBar.target.bind("click",function(e){if(!e)return this;var t=n.findParentNode(e.target,function(e){if(e.getAttribute("data-menu-item-index"))return!0});t&&(s(t,a,"click"),u(t,a,"click")),t=null}),i.menuBar.target.bind("mouseover",function(e){if(!i.menuBar.opened)return!1;var t=n.findParentNode(e.target,function(e){if(e.getAttribute("data-menu-item-index"))return!0});t&&u(t,a,"mouseover"),t=null}),t=null,a=null,o=null,m=null,c=null,this}}(),this.close=function(){return i.menuBar&&i.menuBar.target&&(i.menuBar.target.find("[data-menu-item-index]").removeClass("hover"),i.menuBar.opened=!1,i.menuBar.openedIndex=null),a.call(this,!1),this.queue.forEach(function(e){e.$target.remove()}),this.queue=[],u.call(this,null,{self:this,state:"close"}),this},this.getCheckValue=function(){var e={},t=function(i){for(var l=i.length;l--;)i[l].check&&i[l].check.checked&&(e[i[l].check.name]?(n.isString(e[i[l].check.name])&&(e[i[l].check.name]=[e[i[l].check.name]]),e[i[l].check.name].push(i[l].check.value)):e[i[l].check.name]=i[l].check.value),i[l].items&&i[l].items.length>0&&t(i[l].items)};t(l.items);try{return e}finally{e=null,t=null}},this.main=function(){t.menu_instance=t.menu_instance||[],t.menu_instance.push(this),arguments&&n.isObject(arguments[0])&&this.setConfig(arguments[0])}.apply(this,arguments)}}()),e=ax5.ui.menu}(),function(){var e=ax5.ui.menu,t=function(e){return'\n
\n
\n {{#'+e.items+'}}\n {{^@isMenu}}\n {{#divide}}\n
\n {{/divide}}\n {{#html}}\n
{{{@html}}}
\n {{/html}}\n {{/@isMenu}}\n {{#@isMenu}}\n
\n \n {{#check}}\n \n {{/check}}\n {{^check}}\n \n {{/check}}\n \n {{#icon}}\n {{{.}}}\n {{/icon}}\n {{{'+e.label+'}}}\n {{#accelerator}}\n {{.}}\n {{/accelerator}}\n {{#@hasChild}}\n {{{cfg.icons.arrow}}}\n {{/@hasChild}}\n
\n {{/@isMenu}}\n\n {{/'+e.items+'}}\n
\n
\n
\n '},n=function(e){return'\n
\n
\n {{#'+e.items+'}}\n {{^@isMenu}}\n {{#divide}}\n
\n {{/divide}}\n {{#html}}\n
{{{@html}}}
\n {{/html}}\n {{/@isMenu}}\n {{#@isMenu}}\n
\n {{#icon}}\n {{{.}}}\n {{/icon}}\n {{{'+e.label+"}}}\n
\n {{/@isMenu}}\n {{/"+e.items+"}}\n
\n
\n "};e.tmpl={tmpl:t,tmplMenubar:n,get:function(t,n,i){return ax5.mustache.render(e.tmpl[t].call(this,i),n)}}}(); 2 | //# sourceMappingURL=ax5menu.min.js.map 3 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
28 | 29 |
30 | 31 | 32 | 326 | 327 | 328 | -------------------------------------------------------------------------------- /src/scss/_ax5menu.scss: -------------------------------------------------------------------------------- 1 | @mixin ax-menu() { 2 | box-sizing: border-box; 3 | *, 4 | *:before, 5 | *:after { 6 | box-sizing: border-box; 7 | } 8 | 9 | z-index: $ax5menu-z-index; 10 | position: fixed; 11 | left: 0px; 12 | top: 0px; 13 | #{$hack_ie67}width: 100px; 14 | //overflow: hidden; 15 | opacity: 0.95; 16 | } 17 | 18 | @mixin menu-variant($text-color, $border-color, $heading-bg-color, $item-bg, $item-color, $item-hover-bg, $item-hover-color) { 19 | @include ax-background($item-bg); 20 | border: $ax5menu-inner-border; 21 | border-color: $border-color; 22 | border-radius: $ax5menu-border-radius; 23 | box-shadow: $ax5menu-box-shadow; 24 | color: $text-color; 25 | 26 | .ax-menu-heading { 27 | font-weight: 600; 28 | padding: $ax5menu-heading-padding; 29 | border-bottom: 1px solid transparent; 30 | @include border-top-radius($ax5menu-border-radius - 1); 31 | 32 | color: $text-color; 33 | @include ax-background($heading-bg-color); 34 | .badge { 35 | font-size: 0.8em; 36 | color: $heading-bg-color; 37 | @include ax-background($text-color); 38 | } 39 | } 40 | .ax-menu-body { 41 | padding: $ax5menu-body-padding; 42 | text-align: center; 43 | position: relative; 44 | overflow: hidden; 45 | 46 | $ax-menu-item-height: $ax5menu-item-font-size + 5; 47 | .ax-menu-item { 48 | padding: $ax5menu-item-padding; 49 | text-align: left; 50 | background: $item-bg; 51 | color: $item-color; 52 | cursor: pointer; 53 | font-size: $ax5menu-item-font-size; 54 | 55 | display: table; 56 | position: relative; 57 | border-collapse: separate; 58 | box-sizing: border-box; 59 | overflow: hidden; 60 | //table-layout: fixed; 61 | width: 100%; 62 | height: $ax-menu-item-height; 63 | 64 | .ax-menu-item-cell { 65 | box-sizing: border-box; 66 | display: table-cell; 67 | vertical-align: middle; 68 | white-space: nowrap; 69 | font-size: $ax5menu-item-font-size; 70 | line-height: $ax-menu-item-height; 71 | padding: 0px 0px 0px 0px; 72 | 73 | user-select: none; 74 | 75 | &.ax-menu-item-checkbox { 76 | overflow: hidden; 77 | width: $ax5menu-item-checkbox-width; 78 | text-align: center; 79 | .item-checkbox-wrap { 80 | position: relative; 81 | display: block; 82 | width: $ax5menu-item-checkbox-width; 83 | height: $ax-menu-item-height; 84 | 85 | &.useCheckBox { 86 | &:after { 87 | content: ''; 88 | width: 10px; 89 | height: 5px; 90 | position: absolute; 91 | top: ($ax-menu-item-height - 10) / 2; 92 | left: ($ax-menu-item-height - 10) / 2; 93 | border: 2px solid $item-color; 94 | border-top: none; 95 | border-right: none; 96 | background: transparent; 97 | opacity: 0.1; 98 | @include transform(rotate(-50deg)); 99 | } 100 | &[data-item-checked="true"] { 101 | &:after { 102 | opacity: 1; 103 | } 104 | } 105 | } 106 | } 107 | } 108 | &.ax-menu-item-icon { 109 | text-align: left; 110 | } 111 | &.ax-menu-item-label { 112 | padding-right: 10px; 113 | } 114 | &.ax-menu-item-accelerator { 115 | text-align: right; 116 | padding: 0px 7px 0px 0px; 117 | .item-wrap { 118 | width: 100%; 119 | vertical-align: middle; 120 | @include ellipsis(); 121 | display: block; 122 | } 123 | } 124 | &.ax-menu-item-handle { 125 | overflow: hidden; 126 | width: $ax5menu-item-handle-width; 127 | text-align: center; 128 | } 129 | } 130 | 131 | &:hover, &.hover { 132 | background: $item-hover-bg; 133 | color: $item-hover-color; 134 | .ax-menu-item-cell { 135 | &.ax-menu-item-checkbox { 136 | .item-checkbox-wrap { 137 | &:after { 138 | border-color: $item-hover-color; 139 | } 140 | } 141 | } 142 | } 143 | } 144 | } 145 | .ax-menu-item-divide { 146 | border-top: $ax5menu-inner-border; 147 | border-color: lighten($border-color, 0%); 148 | margin: $ax5menu-body-padding; 149 | } 150 | .ax-menu-item-html { 151 | padding: $ax5menu-item-html-padding; 152 | text-align: left; 153 | } 154 | .ax-menu-buttons { 155 | button { 156 | &:not(:last-child) { 157 | margin-right: 3px; 158 | } 159 | } 160 | } 161 | } 162 | 163 | &.direction-top { 164 | @include ax-border-radius(0, "top"); 165 | @include ax-border-radius($ax5menu-border-radius, "bottom"); 166 | 167 | &.with-arrow { 168 | .ax-menu-arrow { 169 | @include menu-arrow($ax5menu-arrow-size, $ax5menu-arrow-border-width, $border-color, top); 170 | } 171 | } 172 | } 173 | &.direction-right { 174 | @include ax-border-radius(0, "right"); 175 | @include ax-border-radius($ax5menu-border-radius, "left"); 176 | 177 | &.with-arrow { 178 | .ax-menu-arrow { 179 | @include menu-arrow($ax5menu-arrow-size, $ax5menu-arrow-border-width, $border-color, right); 180 | } 181 | } 182 | } 183 | &.direction-bottom { 184 | @include ax-border-radius(0, "bottom"); 185 | @include ax-border-radius($ax5menu-border-radius, "top"); 186 | 187 | &.with-arrow { 188 | .ax-menu-arrow { 189 | @include menu-arrow($ax5menu-arrow-size, $ax5menu-arrow-border-width, $border-color, bottom); 190 | } 191 | } 192 | } 193 | &.direction-left { 194 | @include ax-border-radius(0, "right"); 195 | @include ax-border-radius($ax5menu-border-radius, "right"); 196 | 197 | &.with-arrow { 198 | .ax-menu-arrow { 199 | @include menu-arrow($ax5menu-arrow-size, $ax5menu-arrow-border-width, $border-color, left); 200 | } 201 | } 202 | } 203 | } 204 | 205 | @mixin menu-arrow($arrow-size, $arrow-border-width, $border-color, $arrow-direction) { 206 | 207 | //@debug( nth($ax5menu-inner-border, 3) ); 208 | $arrow-bg: nth($ax5menu-bg, 1); 209 | $arrow-border-color: $border-color; 210 | 211 | position: absolute; 212 | width: 0; 213 | height: 0; 214 | 215 | @if ($arrow-direction == top) { 216 | left: 50%; 217 | top: 0px; 218 | } @else if ($arrow-direction == right) { 219 | right: 0px; 220 | top: 50%; 221 | } @else if ($arrow-direction == bottom) { 222 | left: 50%; 223 | bottom: 0px; 224 | } @else if ($arrow-direction == left) { 225 | left: 0px; 226 | top: 50%; 227 | } 228 | 229 | &:before { 230 | content: ' '; 231 | position: absolute; 232 | width: 0; 233 | height: 0; 234 | 235 | @if ($arrow-direction == top) { 236 | left: - ($arrow-size); 237 | top: - ($arrow-size * 2); 238 | border-left: $arrow-size solid transparent; 239 | border-right: $arrow-size solid transparent; 240 | border-bottom: ($arrow-size * 2) solid $arrow-border-color; 241 | } @else if ($arrow-direction == right) { 242 | right: - ($arrow-size * 2); 243 | top: - ($arrow-size); 244 | border-top: $arrow-size solid transparent; 245 | border-bottom: $arrow-size solid transparent; 246 | border-left: ($arrow-size * 2) solid $arrow-border-color; 247 | } @else if ($arrow-direction == bottom) { 248 | left: - ($arrow-size); 249 | bottom: - ($arrow-size * 2); 250 | border-left: $arrow-size solid transparent; 251 | border-right: $arrow-size solid transparent; 252 | border-top: ($arrow-size * 2) solid $arrow-border-color; 253 | } @else if ($arrow-direction == left) { 254 | left: - ($arrow-size * 2); 255 | top: - ($arrow-size); 256 | border-top: $arrow-size solid transparent; 257 | border-bottom: $arrow-size solid transparent; 258 | border-right: ($arrow-size * 2) solid $arrow-border-color; 259 | } 260 | } 261 | 262 | &:after { 263 | content: ' '; 264 | position: absolute; 265 | width: 0; 266 | height: 0; 267 | 268 | @if ($arrow-direction == top) { 269 | left: - ($arrow-size); 270 | top: - ($arrow-size * 2) + ($arrow-border-width * 2); 271 | border-left: ($arrow-size) solid transparent; 272 | border-right: ($arrow-size) solid transparent; 273 | border-bottom: ($arrow-size * 2) solid $arrow-bg; 274 | } @else if ($arrow-direction == right) { 275 | right: - ($arrow-size * 2) + ($arrow-border-width * 2); 276 | top: - ($arrow-size); 277 | border-top: ($arrow-size) solid transparent; 278 | border-bottom: ($arrow-size) solid transparent; 279 | border-left: ($arrow-size * 2) solid $arrow-bg; 280 | } @else if ($arrow-direction == bottom) { 281 | left: - ($arrow-size); 282 | bottom: - ($arrow-size * 2) + ($arrow-border-width * 2); 283 | border-left: ($arrow-size) solid transparent; 284 | border-right: ($arrow-size) solid transparent; 285 | border-top: ($arrow-size * 2) solid $arrow-bg; 286 | } @else if ($arrow-direction == left) { 287 | left: - ($arrow-size * 2) + ($arrow-border-width * 2); 288 | top: - ($arrow-size); 289 | border-top: ($arrow-size) solid transparent; 290 | border-bottom: ($arrow-size) solid transparent; 291 | border-right: ($arrow-size * 2) solid $arrow-bg; 292 | } 293 | } 294 | } 295 | 296 | @mixin ax-menubar() { 297 | box-sizing: border-box; 298 | height: 100%; 299 | position: relative; 300 | .ax-menu-body { 301 | display: table; 302 | height: 100%; 303 | border-collapse: separate; 304 | box-sizing: border-box; 305 | 306 | .ax-menu-item { 307 | display: table-cell; 308 | height: 100%; 309 | vertical-align: middle; 310 | white-space: nowrap; 311 | box-sizing: border-box; 312 | padding: $ax5menubar-item-padding; 313 | cursor: pointer; 314 | font-size: $ax5menu-item-font-size; 315 | 316 | .ax-menu-item-cell { 317 | white-space: nowrap; 318 | user-select: none; 319 | } 320 | } 321 | } 322 | } 323 | 324 | @mixin menubar-variant($text-color, $border-color, $heading-bg-color, $item-bg, $item-color, $item-hover-bg, $item-hover-color) { 325 | .ax-menu-body { 326 | .ax-menu-item { 327 | color: $item-color; 328 | .ax-menu-item-cell { 329 | 330 | } 331 | &:hover, &.hover { 332 | background: $item-hover-bg; 333 | color: $item-hover-color; 334 | } 335 | } 336 | } 337 | } 338 | 339 | @include keyframes(ax-menu) { 340 | 0% { 341 | opacity: 0.0; 342 | //@include transform(scale(1)); 343 | } 344 | 1% { 345 | opacity: 0.0; 346 | //@include transform(scale(0.3)); 347 | } 348 | 100% { 349 | opacity: 0.95; 350 | //@include transform(scale(1)); 351 | } 352 | } 353 | 354 | @include keyframes(ax-menu-destroy) { 355 | from { 356 | @include transform(scale(1)); 357 | opacity: 1.0; 358 | } 359 | to { 360 | @include transform(scale(0.5)); 361 | opacity: 0.0; 362 | } 363 | } 364 | 365 | // mixins --------------------------------------------- end 366 | 367 | .ax5-ui-menu { 368 | @include ax-menu(); 369 | 370 | @include perspective(1000px); 371 | @include transform-style(preserve-3d); 372 | 373 | @include animation(ax-menu $ax5menu-easing-time-open $ease-out-back); 374 | @include transform(translateZ(0)); 375 | @include transform-origin(center top); 376 | /* flip type 377 | @include backface-visibility(visible); 378 | @include transform(translateY(0%) rotateX(0deg)); 379 | */ 380 | 381 | @include menu-variant($ax5menu-default-text, $ax5menu-default-border-color, $ax5menu-default-heading-bg, 382 | $ax5menu-default-item-bg, $ax5menu-default-item-text, $ax5menu-default-item-hover-bg, $ax5menu-default-item-hover-text); 383 | 384 | &.primary { 385 | @include menu-variant($ax5menu-primary-text, $ax5menu-primary-border-color, $ax5menu-primary-heading-bg, 386 | $ax5menu-primary-item-bg, $ax5menu-primary-item-text, $ax5menu-primary-item-hover-bg, $ax5menu-primary-item-hover-text); 387 | } 388 | &.success { 389 | @include menu-variant($ax5menu-success-text, $ax5menu-success-border-color, $ax5menu-success-heading-bg, 390 | $ax5menu-success-item-bg, $ax5menu-success-item-text, $ax5menu-success-item-hover-bg, $ax5menu-success-item-hover-text); 391 | } 392 | &.info { 393 | @include menu-variant($ax5menu-info-text, $ax5menu-info-border-color, $ax5menu-info-heading-bg, 394 | $ax5menu-info-item-bg, $ax5menu-info-item-text, $ax5menu-info-item-hover-bg, $ax5menu-info-item-hover-text); 395 | } 396 | &.warning { 397 | @include menu-variant($ax5menu-warning-text, $ax5menu-warning-border-color, $ax5menu-warning-heading-bg, 398 | $ax5menu-warning-item-bg, $ax5menu-warning-item-text, $ax5menu-warning-item-hover-bg, $ax5menu-warning-item-hover-text); 399 | } 400 | &.danger { 401 | @include menu-variant($ax5menu-danger-text, $ax5menu-danger-border-color, $ax5menu-danger-heading-bg, 402 | $ax5menu-danger-item-bg, $ax5menu-danger-item-text, $ax5menu-danger-item-hover-bg, $ax5menu-danger-item-hover-text); 403 | } 404 | &.destroy { 405 | @include animation(ax-menu-destroy $ax5menu-easing-time-close $ease-in-back forwards); 406 | } 407 | &.direction-top { 408 | @include transform-origin(center top); 409 | } 410 | &.direction-right { 411 | @include transform-origin(right center); 412 | } 413 | &.direction-bottom { 414 | @include transform-origin(center bottom); 415 | 416 | } 417 | &.direction-left { 418 | @include transform-origin(left center); 419 | } 420 | } 421 | 422 | .ax5-ui-menubar { 423 | @include ax-menubar(); 424 | @include menubar-variant($ax5menu-default-text, $ax5menu-default-border-color, $ax5menu-default-heading-bg, 425 | $ax5menu-default-item-bg, $ax5menu-default-item-text, $ax5menu-default-item-hover-bg, $ax5menu-default-item-hover-text); 426 | 427 | &.primary { 428 | @include menubar-variant($ax5menu-primary-text, $ax5menu-primary-border-color, $ax5menu-primary-heading-bg, 429 | $ax5menu-primary-item-bg, $ax5menu-primary-item-text, $ax5menu-primary-item-hover-bg, $ax5menu-primary-item-hover-text); 430 | } 431 | &.success { 432 | @include menubar-variant($ax5menu-success-text, $ax5menu-success-border-color, $ax5menu-success-heading-bg, 433 | $ax5menu-success-item-bg, $ax5menu-success-item-text, $ax5menu-success-item-hover-bg, $ax5menu-success-item-hover-text); 434 | } 435 | &.info { 436 | @include menubar-variant($ax5menu-info-text, $ax5menu-info-border-color, $ax5menu-info-heading-bg, 437 | $ax5menu-info-item-bg, $ax5menu-info-item-text, $ax5menu-info-item-hover-bg, $ax5menu-info-item-hover-text); 438 | } 439 | &.warning { 440 | @include menubar-variant($ax5menu-warning-text, $ax5menu-warning-border-color, $ax5menu-warning-heading-bg, 441 | $ax5menu-warning-item-bg, $ax5menu-warning-item-text, $ax5menu-warning-item-hover-bg, $ax5menu-warning-item-hover-text); 442 | } 443 | &.danger { 444 | @include menubar-variant($ax5menu-danger-text, $ax5menu-danger-border-color, $ax5menu-danger-heading-bg, 445 | $ax5menu-danger-item-bg, $ax5menu-danger-item-text, $ax5menu-danger-item-hover-bg, $ax5menu-danger-item-hover-text); 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /src/ax5menu.js: -------------------------------------------------------------------------------- 1 | // ax5.ui.menu 2 | (function () { 3 | var UI = ax5.ui; 4 | var U = ax5.util; 5 | var MENU; 6 | 7 | UI.addClass({ 8 | className: "menu" 9 | }, (function () { 10 | /** 11 | * @class ax5.ui.menu 12 | * @classdesc 13 | * @author tom@axisj.com 14 | * @example 15 | * ```js 16 | * var menu = new ax5.ui.menu({ 17 | * theme: 'primary', 18 | * iconWidth: 20, 19 | * acceleratorWidth: 100, 20 | * itemClickAndClose: false, 21 | * icons: { 22 | * 'arrow': '' 23 | * }, 24 | * columnKeys: { 25 | * label: 'name', 26 | * items: 'chidren' 27 | * }, 28 | * items: [ 29 | * { 30 | * icon: '', 31 | * name: "Menu Parent 0", 32 | * chidren: [ 33 | * { 34 | * check: { 35 | * type: 'checkbox', 36 | * name: 'A', 37 | * value: '0', 38 | * checked: false 39 | * }, 40 | * name: "Menu Z", 41 | * data: {}, 42 | * role: "", 43 | * accelerator: "CmdOrCtrl+Z" 44 | * }, 45 | * { 46 | * check: { 47 | * type: 'checkbox', 48 | * name: 'A', 49 | * value: '1', 50 | * checked: true 51 | * }, 52 | * name: "Menu A", 53 | * data: {}, 54 | * role: "" 55 | * } 56 | * ], 57 | * filterType: "A" 58 | * }, 59 | * { 60 | * divide: true, 61 | * filterType: "A" 62 | * }, 63 | * { 64 | * icon: '', 65 | * name: "Menu Parent 1", 66 | * chidren: [ 67 | * { 68 | * name: "Menu Z", 69 | * data: {}, 70 | * role: "", 71 | * chidren: [ 72 | * { 73 | * name: "Menu Z", 74 | * data: {}, 75 | * role: "" 76 | * }, 77 | * { 78 | * name: "Menu A", 79 | * data: {}, 80 | * role: "" 81 | * } 82 | * ] 83 | * }, 84 | * { 85 | * name: "Menu A", 86 | * data: {}, 87 | * role: "" 88 | * } 89 | * ], 90 | * filterType: "A" 91 | * }, 92 | * { 93 | * check: { 94 | * type: 'radio', 95 | * name: 'radioName', 96 | * value: '1', 97 | * checked: false 98 | * }, 99 | * icon: '', 100 | * name: "Menu Parent 2" 101 | * }, 102 | * { 103 | * check: { 104 | * type: 'radio', 105 | * name: 'radioName', 106 | * value: '2', 107 | * checked: false 108 | * }, 109 | * name: "Menu Parent 3" 110 | * }, 111 | * { 112 | * check: { 113 | * type: 'radio', 114 | * name: 'radioName', 115 | * value: '3', 116 | * checked: false 117 | * }, 118 | * name: "Menu Parent 4" 119 | * }, 120 | * {divide: true}, 121 | * { 122 | * html: function () { 123 | * return '
' + 124 | * ' ' + 125 | * '' + 126 | * '
'; 127 | * } 128 | * } 129 | * ] 130 | * }); 131 | * ``` 132 | */ 133 | return function () { 134 | let self = this, 135 | cfg; 136 | 137 | this.instanceId = ax5.getGuid(); 138 | this.config = { 139 | theme: "default", 140 | iconWidth: 22, 141 | acceleratorWidth: 100, 142 | menuBodyPadding: 5, 143 | //direction: "top", // top|bottom 144 | offset: {left: 0, top: 0}, 145 | position: "fixed", 146 | animateTime: 250, 147 | items: [], 148 | itemClickAndClose: true, 149 | columnKeys: { 150 | label: 'label', 151 | items: 'items' 152 | } 153 | }; 154 | 155 | this.openTimer = null; 156 | this.closeTimer = null; 157 | this.queue = []; 158 | this.menuBar = {}; 159 | this.state = undefined; 160 | 161 | cfg = this.config; 162 | 163 | const appEventAttach = function (active, opt) { 164 | if (active) { 165 | jQuery(document.body) 166 | .off("click.ax5menu-" + this.instanceId) 167 | .on("click.ax5menu-" + this.instanceId, clickItem.bind(this, opt)); 168 | 169 | jQuery(window) 170 | .off("keydown.ax5menu-" + this.instanceId) 171 | .on("keydown.ax5menu-" + this.instanceId, function (e) { 172 | if (e.which == ax5.info.eventKeys.ESC) { 173 | self.close(); 174 | } 175 | }); 176 | 177 | jQuery(window) 178 | .on("resize.ax5menu-" + this.instanceId) 179 | .on("resize.ax5menu-" + this.instanceId, function (e) { 180 | self.close(); 181 | }); 182 | } 183 | else { 184 | jQuery(document.body).off("click.ax5menu-" + this.instanceId); 185 | jQuery(window).off("keydown.ax5menu-" + this.instanceId); 186 | jQuery(window).off("resize.ax5menu-" + this.instanceId); 187 | } 188 | }; 189 | 190 | const onStateChanged = function (opts, that) { 191 | if (opts && opts.onStateChanged) { 192 | opts.onStateChanged.call(that, that); 193 | } 194 | else if (this.onStateChanged) { 195 | this.onStateChanged.call(that, that); 196 | } 197 | 198 | self.state = that.state; 199 | opts = null; 200 | that = null; 201 | return true; 202 | }; 203 | 204 | const onLoad = function (that) { 205 | if (this.onLoad) { 206 | this.onLoad.call(that, that); 207 | } 208 | 209 | that = null; 210 | return true; 211 | }; 212 | 213 | const popup = function (opt, items, depth, path) { 214 | let data = opt, 215 | activeMenu, 216 | removed 217 | ; 218 | 219 | data.theme = opt.theme || cfg.theme; 220 | data.cfg = { 221 | icons: jQuery.extend({}, cfg.icons), 222 | iconWidth: opt.iconWidth || cfg.iconWidth, 223 | acceleratorWidth: opt.acceleratorWidth || cfg.acceleratorWidth 224 | }; 225 | 226 | items.forEach(function (n) { 227 | if (n.html || n.divide) { 228 | n['@isMenu'] = false; 229 | if (n.html) { 230 | n['@html'] = n.html.call({ 231 | item: n, 232 | config: cfg, 233 | opt: opt 234 | }); 235 | } 236 | } 237 | else { 238 | n['@isMenu'] = true; 239 | } 240 | }); 241 | 242 | data[cfg.columnKeys.items] = items; 243 | data['@depth'] = depth; 244 | data['@path'] = path || "root"; 245 | data['@hasChild'] = function () { 246 | return this[cfg.columnKeys.items] && this[cfg.columnKeys.items].length > 0; 247 | }; 248 | activeMenu = jQuery(MENU.tmpl.get.call(this, "tmpl", data, cfg.columnKeys)); 249 | jQuery(document.body).append(activeMenu); 250 | 251 | // remove queue 252 | 253 | removed = this.queue.splice(depth); 254 | removed.forEach(function (n) { 255 | n.$target.remove(); 256 | }); 257 | 258 | this.queue.push({ 259 | '$target': activeMenu, 260 | 'data': jQuery.extend({}, data) 261 | }); 262 | 263 | activeMenu.find('[data-menu-item-index]').bind("mouseover", function () { 264 | let depth = this.getAttribute("data-menu-item-depth"), 265 | index = this.getAttribute("data-menu-item-index"), 266 | path = this.getAttribute("data-menu-item-path"), 267 | $this, 268 | offset, 269 | scrollTop, 270 | childOpt, 271 | _items, 272 | _activeMenu; 273 | 274 | if (depth != null && typeof depth != "undefined") { 275 | _items = self.queue[depth].data[cfg.columnKeys.items][index][cfg.columnKeys.items]; 276 | _activeMenu = self.queue[depth].$target; 277 | _activeMenu.find('[data-menu-item-index]').removeClass("hover"); 278 | jQuery(this).addClass("hover"); 279 | 280 | if (_activeMenu.attr("data-selected-menu-item-index") != index) { 281 | _activeMenu.attr("data-selected-menu-item-index", index); 282 | 283 | if (_items && _items.length > 0) { 284 | 285 | $this = jQuery(this); 286 | offset = $this.offset(); 287 | scrollTop = (cfg.position == "fixed" ? jQuery(document).scrollTop() : 0); 288 | childOpt = { 289 | '@parent': { 290 | left: offset.left, 291 | top: offset.top, 292 | width: $this.outerWidth(), 293 | height: $this.outerHeight() 294 | }, 295 | left: offset.left + $this.outerWidth() - cfg.menuBodyPadding, 296 | top: offset.top - cfg.menuBodyPadding - 1 - scrollTop 297 | }; 298 | 299 | childOpt = jQuery.extend(true, opt, childOpt); 300 | popup.call(self, childOpt, _items, (Number(depth) + 1), path); 301 | } 302 | else { 303 | self.queue.splice(Number(depth) + 1).forEach(function (n) { 304 | n.$target.remove(); 305 | }); 306 | } 307 | } 308 | } 309 | 310 | depth = null; 311 | index = null; 312 | path = null; 313 | $this = null; 314 | offset = null; 315 | scrollTop = null; 316 | childOpt = null; 317 | _items = null; 318 | _activeMenu = null; 319 | }); 320 | 321 | // mouse out 322 | activeMenu.find('[data-menu-item-index]').bind("mouseout", function () { 323 | let depth = this.getAttribute("data-menu-item-depth"), 324 | index = this.getAttribute("data-menu-item-index"), 325 | path = this.getAttribute("data-menu-item-path"), 326 | _items; 327 | 328 | if (path) { 329 | _items = self.queue[depth].data[cfg.columnKeys.items][index][cfg.columnKeys.items]; 330 | } 331 | if (_items && _items.length > 0) { 332 | 333 | } else { 334 | jQuery(this).removeClass("hover"); 335 | } 336 | }); 337 | 338 | // is Root 339 | if (depth == 0) { 340 | if (data.direction) activeMenu.addClass("direction-" + data.direction); 341 | onStateChanged.call(this, null, { 342 | self: this, 343 | items: items, 344 | parent: (function (path) { 345 | if (!path) return false; 346 | var item = null; 347 | try { 348 | item = (Function("", "return this.config.items[" + path.substring(5).replace(/\./g, '].items[') + "];")).call(self); 349 | } catch (e) { 350 | 351 | } 352 | return item; 353 | })(data['@path']), 354 | state: "popup" 355 | }); 356 | } 357 | 358 | align.call(this, activeMenu, data); 359 | onLoad.call(this, { 360 | self: this, 361 | items: items, 362 | element: activeMenu.get(0) 363 | }); 364 | 365 | data = null; 366 | activeMenu = null; 367 | removed = null; 368 | opt = null; 369 | items = null; 370 | depth = null; 371 | path = null; 372 | 373 | return this; 374 | }; 375 | 376 | const clickItem = function (opt, e) { 377 | let target, item; 378 | 379 | target = U.findParentNode(e.target, function (target) { 380 | if (target.getAttribute("data-menu-item-index")) { 381 | return true; 382 | } 383 | }); 384 | if (target) { 385 | if (typeof opt === "undefined") opt = {}; 386 | item = (function (path) { 387 | if (!path) return false; 388 | let item; 389 | 390 | try { 391 | item = (Function("", "return this[" + path.substring(5).replace(/\./g, '].' + cfg.columnKeys.items + '[') + "];")).call(opt.items || cfg.items); 392 | } catch (e) { 393 | console.log(ax5.info.getError("ax5menu", "501", "menuItemClick")); 394 | } 395 | 396 | try { 397 | return item; 398 | } 399 | finally { 400 | item = null; 401 | } 402 | })(target.getAttribute("data-menu-item-path")); 403 | 404 | if (!item) return this; 405 | 406 | if (item.check) { 407 | (function (items) { 408 | var setValue = { 409 | 'checkbox': function (value) { 410 | this.checked = !value; 411 | }, 412 | 'radio': function (value) { 413 | var name = this.name; 414 | items.forEach(function (n) { 415 | if (n.check && n.check.type === 'radio' && n.check.name == name) { 416 | n.check.checked = false; 417 | } 418 | }); 419 | this.checked = !value; 420 | } 421 | }; 422 | if (setValue[this.type]) setValue[this.type].call(this, this.checked); 423 | setValue = null; 424 | }).call(item.check, cfg.items); 425 | 426 | if (!cfg.itemClickAndClose) { 427 | self.queue.forEach(function (n) { 428 | n.$target.find('[data-menu-item-index]').each(function () { 429 | var item = n.data[cfg.columnKeys.items][this.getAttribute("data-menu-item-index")]; 430 | if (item.check) { 431 | jQuery(this).find(".item-checkbox-wrap").attr("data-item-checked", item.check.checked); 432 | } 433 | }); 434 | }); 435 | } 436 | } 437 | 438 | if (self.onClick) { 439 | if (self.onClick.call(item, item, opt.param)) { 440 | self.close(); 441 | } 442 | } 443 | if ((!item[cfg.columnKeys.items] || item[cfg.columnKeys.items].length == 0) && cfg.itemClickAndClose) self.close(); 444 | } 445 | else { 446 | self.close(); 447 | } 448 | 449 | target = null; 450 | item = null; 451 | return this; 452 | }; 453 | 454 | const align = function (activeMenu, data) { 455 | let $window = jQuery(window), 456 | $document = jQuery(document), 457 | wh = (cfg.position == "fixed") ? $window.height() : $document.height(), 458 | ww = $window.width(), 459 | h = activeMenu.outerHeight(), 460 | w = activeMenu.outerWidth(), 461 | l = data.left, 462 | t = data.top, 463 | position = cfg.position || "fixed"; 464 | 465 | if (l + w > ww) { 466 | if (data['@parent']) { 467 | l = data['@parent'].left - w + cfg.menuBodyPadding; 468 | } 469 | else { 470 | l = ww - w; 471 | } 472 | } 473 | 474 | if (t + h > wh) { 475 | t = wh - h; 476 | } 477 | 478 | activeMenu.css({left: l, top: t, position: position}); 479 | 480 | activeMenu = null; 481 | data = null; 482 | $window = null; 483 | $document = null; 484 | wh = null; 485 | ww = null; 486 | h = null; 487 | w = null; 488 | l = null; 489 | t = null; 490 | position = null; 491 | return this; 492 | }; 493 | 494 | /// private end 495 | 496 | this.init = function () { 497 | /** 498 | * config에 선언된 이벤트 함수들을 this로 이동시켜 주어 나중에 인스턴스.on... 으로 처리 가능 하도록 변경 499 | */ 500 | this.onStateChanged = cfg.onStateChanged; 501 | this.onClick = cfg.onClick; 502 | this.onLoad = cfg.onLoad; 503 | 504 | onStateChanged.call(this, null, { 505 | self: this, 506 | state: "init" 507 | }); 508 | }; 509 | 510 | /** 511 | * @method ax5.ui.menu.popup 512 | * @param {Event|Object} e - Event or Object 513 | * @param {Object} [opt] 514 | * @param {String} [opt.theme] 515 | * @param {Function} [opt.filter] 516 | * @returns {ax5.ui.menu} this 517 | */ 518 | this.popup = (function () { 519 | 520 | let getOption = { 521 | 'event': function (e, opt) { 522 | //var xOffset = Math.max(document.documentElement.scrollLeft, document.body.scrollLeft); 523 | //var yOffset = Math.max(document.documentElement.scrollTop, document.body.scrollTop); 524 | //console.log(e.pageY); 525 | 526 | e = { 527 | left: e.clientX, 528 | top: (cfg.position == "fixed") ? e.clientY : e.pageY, 529 | width: cfg.width, 530 | theme: cfg.theme 531 | }; 532 | 533 | e.left -= 5; 534 | e.top -= 5; 535 | 536 | if (cfg.offset) { 537 | if (cfg.offset.left) e.left += cfg.offset.left; 538 | if (cfg.offset.top) e.top += cfg.offset.top; 539 | } 540 | opt = jQuery.extend(true, e, opt); 541 | 542 | try { 543 | return opt; 544 | } 545 | finally { 546 | e = null; 547 | //opt = null; 548 | } 549 | }, 550 | 'object': function (e, opt) { 551 | e = { 552 | left: e.left, 553 | top: e.top, 554 | width: e.width || cfg.width, 555 | theme: e.theme || cfg.theme 556 | }; 557 | 558 | if (cfg.offset) { 559 | if (cfg.offset.left) e.left += cfg.offset.left; 560 | if (cfg.offset.top) e.top += cfg.offset.top; 561 | } 562 | 563 | opt = jQuery.extend(true, e, opt); 564 | 565 | try { 566 | return opt; 567 | } 568 | finally { 569 | e = null; 570 | //opt = null; 571 | } 572 | } 573 | }, 574 | updateTheme = function (theme) { 575 | if (theme) cfg.theme = theme; 576 | }; 577 | 578 | return function (e, opt) { 579 | 580 | if (!e) return this; 581 | opt = getOption[((typeof e.clientX == "undefined") ? "object" : "event")].call(this, e, opt); 582 | updateTheme(opt.theme); 583 | 584 | let items = [].concat(cfg.items), 585 | filteringItem; 586 | opt.items = items; 587 | 588 | if (opt.filter) { 589 | filteringItem = function (_items) { 590 | let arr = []; 591 | _items.forEach(function (n) { 592 | if (n.items && n.items.length > 0) { 593 | n.items = filteringItem(n.items); 594 | } 595 | if (opt.filter.call(n)) { 596 | arr.push(n); 597 | } 598 | }); 599 | return arr; 600 | }; 601 | opt.items = items = filteringItem(items); 602 | } 603 | 604 | if (items.length) { 605 | appEventAttach.call(this, false); 606 | popup.call(this, opt, items, 0); // 0 is seq of queue 607 | 608 | if (this.popupEventAttachTimer) clearTimeout(this.popupEventAttachTimer); 609 | this.popupEventAttachTimer = setTimeout((function () { 610 | appEventAttach.call(this, true, opt); // 이벤트 연결 611 | }).bind(this), 500); 612 | } 613 | 614 | e = null; 615 | return this; 616 | } 617 | })(); 618 | 619 | /** 620 | * @method ax5.ui.menu.attach 621 | * @param {Element|jQueryObject} el 622 | * @returns {ax5.ui.menu} this 623 | */ 624 | this.attach = (function () { 625 | 626 | var getOption = { 627 | 'object': function (e, opt) { 628 | e = { 629 | left: e.left, 630 | top: e.top, 631 | width: e.width || cfg.width, 632 | theme: e.theme || cfg.theme, 633 | direction: e.direction || cfg.direction 634 | }; 635 | opt = jQuery.extend(true, opt, e); 636 | 637 | try { 638 | return opt; 639 | } 640 | finally { 641 | e = null; 642 | opt = null; 643 | } 644 | } 645 | }; 646 | 647 | var popUpChildMenu = function (target, opt, eType) { 648 | var 649 | $target = jQuery(target), 650 | offset = $target.offset(), 651 | height = $target.outerHeight(), 652 | index = Number(target.getAttribute("data-menu-item-index")), 653 | scrollTop = (cfg.position == "fixed") ? jQuery(document).scrollTop() : 0; 654 | 655 | if (cfg.items && cfg.items[index][cfg.columnKeys.items] && cfg.items[index][cfg.columnKeys.items].length) { 656 | 657 | if (self.menuBar.openedIndex == index) { 658 | if (eType == "click") self.close(); 659 | return false; 660 | } 661 | 662 | self.menuBar.target.find('[data-menu-item-index]').removeClass("hover"); 663 | self.menuBar.opened = true; 664 | self.menuBar.openedIndex = index; 665 | 666 | $target.attr("data-menu-item-opened", "true"); 667 | $target.addClass("hover"); 668 | 669 | if (cfg.offset) { 670 | if (cfg.offset.left) offset.left += cfg.offset.left; 671 | if (cfg.offset.top) offset.top += cfg.offset.top; 672 | } 673 | 674 | opt = getOption["object"].call(this, {left: offset.left, top: offset.top + height - scrollTop}, opt); 675 | 676 | popup.call(self, opt, cfg.items[index][cfg.columnKeys.items], 0, 'root.' + target.getAttribute("data-menu-item-index")); // 0 is seq of queue 677 | appEventAttach.call(self, true, {}); // 이벤트 연결 678 | } 679 | 680 | target = null; 681 | opt = null; 682 | $target = null; 683 | offset = null; 684 | height = null; 685 | index = null; 686 | scrollTop = null; 687 | }; 688 | var clickParentMenu = function (target, opt, eType) { 689 | var 690 | $target = jQuery(target), 691 | offset = $target.offset(), 692 | height = $target.outerHeight(), 693 | index = Number(target.getAttribute("data-menu-item-index")), 694 | scrollTop = (cfg.position == "fixed") ? jQuery(document).scrollTop() : 0; 695 | if (cfg.items && (!cfg.items[index][cfg.columnKeys.items] || cfg.items[index][cfg.columnKeys.items].length == 0)) { 696 | if (self.onClick) { 697 | self.onClick.call(cfg.items[index], cfg.items[index]); 698 | } 699 | } 700 | }; 701 | 702 | return function (el, opt) { 703 | var 704 | data = {}, 705 | items = cfg.items, 706 | activeMenu; 707 | 708 | if (typeof opt === "undefined") opt = {}; 709 | 710 | data.theme = opt.theme || cfg.theme; 711 | data.cfg = { 712 | icons: jQuery.extend({}, cfg.icons), 713 | iconWidth: opt.iconWidth || cfg.iconWidth, 714 | acceleratorWidth: opt.acceleratorWidth || cfg.acceleratorWidth 715 | }; 716 | 717 | items.forEach(function (n) { 718 | if (n.html || n.divide) { 719 | n['@isMenu'] = false; 720 | if (n.html) { 721 | n['@html'] = n.html.call({ 722 | item: n, 723 | config: cfg, 724 | opt: opt 725 | }); 726 | } 727 | } 728 | else { 729 | n['@isMenu'] = true; 730 | } 731 | }); 732 | 733 | data[cfg.columnKeys.items] = items; 734 | 735 | activeMenu = jQuery(MENU.tmpl.get.call(this, "tmplMenubar", data, cfg.columnKeys)); 736 | self.menuBar = { 737 | target: jQuery(el), 738 | opened: false 739 | }; 740 | self.menuBar.target.html(activeMenu); 741 | 742 | // click, mouseover 743 | self.menuBar.target.bind("click", function (e) { 744 | if (!e) return this; 745 | var target = U.findParentNode(e.target, function (target) { 746 | if (target.getAttribute("data-menu-item-index")) { 747 | return true; 748 | } 749 | }); 750 | if (target) { 751 | clickParentMenu(target, opt, "click"); 752 | popUpChildMenu(target, opt, "click"); 753 | } 754 | 755 | target = null; 756 | }); 757 | self.menuBar.target.bind("mouseover", function (e) { 758 | if (!self.menuBar.opened) return false; 759 | var target = U.findParentNode(e.target, function (target) { 760 | if (target.getAttribute("data-menu-item-index")) { 761 | return true; 762 | } 763 | }); 764 | if (target) popUpChildMenu(target, opt, "mouseover"); 765 | 766 | target = null; 767 | }); 768 | 769 | el = null; 770 | opt = null; 771 | data = null; 772 | items = null; 773 | activeMenu = null; 774 | 775 | return this; 776 | } 777 | })(); 778 | 779 | /** 780 | * @method ax5.ui.menu.close 781 | * @returns {ax5.ui.menu} this 782 | */ 783 | this.close = function () { 784 | 785 | if (self.menuBar && self.menuBar.target) { 786 | self.menuBar.target.find('[data-menu-item-index]').removeClass("hover"); 787 | self.menuBar.opened = false; 788 | self.menuBar.openedIndex = null; 789 | } 790 | 791 | appEventAttach.call(this, false); // 이벤트 제거 792 | 793 | this.queue.forEach(function (n) { 794 | n.$target.remove(); 795 | }); 796 | this.queue = []; 797 | 798 | onStateChanged.call(this, null, { 799 | self: this, 800 | state: "close" 801 | }); 802 | 803 | return this; 804 | }; 805 | 806 | /** 807 | * @method ax5.ui.menu.getCheckValue 808 | * @returns {Object} statusCheckItem 809 | */ 810 | this.getCheckValue = function () { 811 | var checkItems = {}, 812 | collectItem = function (items) { 813 | var i = items.length; 814 | while (i--) { 815 | if (items[i].check && items[i].check.checked) { 816 | if (!checkItems[items[i].check.name]) checkItems[items[i].check.name] = items[i].check.value; 817 | else { 818 | if (U.isString(checkItems[items[i].check.name])) checkItems[items[i].check.name] = [checkItems[items[i].check.name]]; 819 | checkItems[items[i].check.name].push(items[i].check.value); 820 | } 821 | } 822 | if (items[i].items && items[i].items.length > 0) collectItem(items[i].items); 823 | } 824 | }; 825 | 826 | collectItem(cfg.items); 827 | 828 | try { 829 | return checkItems; 830 | } 831 | finally { 832 | checkItems = null; 833 | collectItem = null; 834 | } 835 | }; 836 | 837 | // 클래스 생성자 838 | this.main = (function () { 839 | 840 | UI.menu_instance = UI.menu_instance || []; 841 | UI.menu_instance.push(this); 842 | 843 | if (arguments && U.isObject(arguments[0])) { 844 | this.setConfig(arguments[0]); 845 | } 846 | }).apply(this, arguments); 847 | }; 848 | })()); 849 | 850 | MENU = ax5.ui.menu; 851 | })(); 852 | 853 | // todo : menu 드랍다운 아이콘 설정 기능 추가 -------------------------------------------------------------------------------- /dist/ax5menu.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // ax5.ui.menu 4 | (function () { 5 | var UI = ax5.ui; 6 | var U = ax5.util; 7 | var MENU; 8 | 9 | UI.addClass({ 10 | className: "menu" 11 | }, function () { 12 | /** 13 | * @class ax5.ui.menu 14 | * @classdesc 15 | * @author tom@axisj.com 16 | * @example 17 | * ```js 18 | * var menu = new ax5.ui.menu({ 19 | * theme: 'primary', 20 | * iconWidth: 20, 21 | * acceleratorWidth: 100, 22 | * itemClickAndClose: false, 23 | * icons: { 24 | * 'arrow': '' 25 | * }, 26 | * columnKeys: { 27 | * label: 'name', 28 | * items: 'chidren' 29 | * }, 30 | * items: [ 31 | * { 32 | * icon: '', 33 | * name: "Menu Parent 0", 34 | * chidren: [ 35 | * { 36 | * check: { 37 | * type: 'checkbox', 38 | * name: 'A', 39 | * value: '0', 40 | * checked: false 41 | * }, 42 | * name: "Menu Z", 43 | * data: {}, 44 | * role: "", 45 | * accelerator: "CmdOrCtrl+Z" 46 | * }, 47 | * { 48 | * check: { 49 | * type: 'checkbox', 50 | * name: 'A', 51 | * value: '1', 52 | * checked: true 53 | * }, 54 | * name: "Menu A", 55 | * data: {}, 56 | * role: "" 57 | * } 58 | * ], 59 | * filterType: "A" 60 | * }, 61 | * { 62 | * divide: true, 63 | * filterType: "A" 64 | * }, 65 | * { 66 | * icon: '', 67 | * name: "Menu Parent 1", 68 | * chidren: [ 69 | * { 70 | * name: "Menu Z", 71 | * data: {}, 72 | * role: "", 73 | * chidren: [ 74 | * { 75 | * name: "Menu Z", 76 | * data: {}, 77 | * role: "" 78 | * }, 79 | * { 80 | * name: "Menu A", 81 | * data: {}, 82 | * role: "" 83 | * } 84 | * ] 85 | * }, 86 | * { 87 | * name: "Menu A", 88 | * data: {}, 89 | * role: "" 90 | * } 91 | * ], 92 | * filterType: "A" 93 | * }, 94 | * { 95 | * check: { 96 | * type: 'radio', 97 | * name: 'radioName', 98 | * value: '1', 99 | * checked: false 100 | * }, 101 | * icon: '', 102 | * name: "Menu Parent 2" 103 | * }, 104 | * { 105 | * check: { 106 | * type: 'radio', 107 | * name: 'radioName', 108 | * value: '2', 109 | * checked: false 110 | * }, 111 | * name: "Menu Parent 3" 112 | * }, 113 | * { 114 | * check: { 115 | * type: 'radio', 116 | * name: 'radioName', 117 | * value: '3', 118 | * checked: false 119 | * }, 120 | * name: "Menu Parent 4" 121 | * }, 122 | * {divide: true}, 123 | * { 124 | * html: function () { 125 | * return '
' + 126 | * ' ' + 127 | * '' + 128 | * '
'; 129 | * } 130 | * } 131 | * ] 132 | * }); 133 | * ``` 134 | */ 135 | return function () { 136 | var self = this, 137 | cfg = void 0; 138 | 139 | this.instanceId = ax5.getGuid(); 140 | this.config = { 141 | theme: "default", 142 | iconWidth: 22, 143 | acceleratorWidth: 100, 144 | menuBodyPadding: 5, 145 | //direction: "top", // top|bottom 146 | offset: { left: 0, top: 0 }, 147 | position: "fixed", 148 | animateTime: 250, 149 | items: [], 150 | itemClickAndClose: true, 151 | columnKeys: { 152 | label: 'label', 153 | items: 'items' 154 | } 155 | }; 156 | 157 | this.openTimer = null; 158 | this.closeTimer = null; 159 | this.queue = []; 160 | this.menuBar = {}; 161 | this.state = undefined; 162 | 163 | cfg = this.config; 164 | 165 | var appEventAttach = function appEventAttach(active, opt) { 166 | if (active) { 167 | jQuery(document.body).off("click.ax5menu-" + this.instanceId).on("click.ax5menu-" + this.instanceId, clickItem.bind(this, opt)); 168 | 169 | jQuery(window).off("keydown.ax5menu-" + this.instanceId).on("keydown.ax5menu-" + this.instanceId, function (e) { 170 | if (e.which == ax5.info.eventKeys.ESC) { 171 | self.close(); 172 | } 173 | }); 174 | 175 | jQuery(window).on("resize.ax5menu-" + this.instanceId).on("resize.ax5menu-" + this.instanceId, function (e) { 176 | self.close(); 177 | }); 178 | } else { 179 | jQuery(document.body).off("click.ax5menu-" + this.instanceId); 180 | jQuery(window).off("keydown.ax5menu-" + this.instanceId); 181 | jQuery(window).off("resize.ax5menu-" + this.instanceId); 182 | } 183 | }; 184 | 185 | var onStateChanged = function onStateChanged(opts, that) { 186 | if (opts && opts.onStateChanged) { 187 | opts.onStateChanged.call(that, that); 188 | } else if (this.onStateChanged) { 189 | this.onStateChanged.call(that, that); 190 | } 191 | 192 | self.state = that.state; 193 | opts = null; 194 | that = null; 195 | return true; 196 | }; 197 | 198 | var onLoad = function onLoad(that) { 199 | if (this.onLoad) { 200 | this.onLoad.call(that, that); 201 | } 202 | 203 | that = null; 204 | return true; 205 | }; 206 | 207 | var popup = function popup(opt, items, depth, path) { 208 | var data = opt, 209 | activeMenu = void 0, 210 | removed = void 0; 211 | 212 | data.theme = opt.theme || cfg.theme; 213 | data.cfg = { 214 | icons: jQuery.extend({}, cfg.icons), 215 | iconWidth: opt.iconWidth || cfg.iconWidth, 216 | acceleratorWidth: opt.acceleratorWidth || cfg.acceleratorWidth 217 | }; 218 | 219 | items.forEach(function (n) { 220 | if (n.html || n.divide) { 221 | n['@isMenu'] = false; 222 | if (n.html) { 223 | n['@html'] = n.html.call({ 224 | item: n, 225 | config: cfg, 226 | opt: opt 227 | }); 228 | } 229 | } else { 230 | n['@isMenu'] = true; 231 | } 232 | }); 233 | 234 | data[cfg.columnKeys.items] = items; 235 | data['@depth'] = depth; 236 | data['@path'] = path || "root"; 237 | data['@hasChild'] = function () { 238 | return this[cfg.columnKeys.items] && this[cfg.columnKeys.items].length > 0; 239 | }; 240 | activeMenu = jQuery(MENU.tmpl.get.call(this, "tmpl", data, cfg.columnKeys)); 241 | jQuery(document.body).append(activeMenu); 242 | 243 | // remove queue 244 | 245 | removed = this.queue.splice(depth); 246 | removed.forEach(function (n) { 247 | n.$target.remove(); 248 | }); 249 | 250 | this.queue.push({ 251 | '$target': activeMenu, 252 | 'data': jQuery.extend({}, data) 253 | }); 254 | 255 | activeMenu.find('[data-menu-item-index]').bind("mouseover", function () { 256 | var depth = this.getAttribute("data-menu-item-depth"), 257 | index = this.getAttribute("data-menu-item-index"), 258 | path = this.getAttribute("data-menu-item-path"), 259 | $this = void 0, 260 | offset = void 0, 261 | scrollTop = void 0, 262 | childOpt = void 0, 263 | _items = void 0, 264 | _activeMenu = void 0; 265 | 266 | if (depth != null && typeof depth != "undefined") { 267 | _items = self.queue[depth].data[cfg.columnKeys.items][index][cfg.columnKeys.items]; 268 | _activeMenu = self.queue[depth].$target; 269 | _activeMenu.find('[data-menu-item-index]').removeClass("hover"); 270 | jQuery(this).addClass("hover"); 271 | 272 | if (_activeMenu.attr("data-selected-menu-item-index") != index) { 273 | _activeMenu.attr("data-selected-menu-item-index", index); 274 | 275 | if (_items && _items.length > 0) { 276 | 277 | $this = jQuery(this); 278 | offset = $this.offset(); 279 | scrollTop = cfg.position == "fixed" ? jQuery(document).scrollTop() : 0; 280 | childOpt = { 281 | '@parent': { 282 | left: offset.left, 283 | top: offset.top, 284 | width: $this.outerWidth(), 285 | height: $this.outerHeight() 286 | }, 287 | left: offset.left + $this.outerWidth() - cfg.menuBodyPadding, 288 | top: offset.top - cfg.menuBodyPadding - 1 - scrollTop 289 | }; 290 | 291 | childOpt = jQuery.extend(true, opt, childOpt); 292 | popup.call(self, childOpt, _items, Number(depth) + 1, path); 293 | } else { 294 | self.queue.splice(Number(depth) + 1).forEach(function (n) { 295 | n.$target.remove(); 296 | }); 297 | } 298 | } 299 | } 300 | 301 | depth = null; 302 | index = null; 303 | path = null; 304 | $this = null; 305 | offset = null; 306 | scrollTop = null; 307 | childOpt = null; 308 | _items = null; 309 | _activeMenu = null; 310 | }); 311 | 312 | // mouse out 313 | activeMenu.find('[data-menu-item-index]').bind("mouseout", function () { 314 | var depth = this.getAttribute("data-menu-item-depth"), 315 | index = this.getAttribute("data-menu-item-index"), 316 | path = this.getAttribute("data-menu-item-path"), 317 | _items = void 0; 318 | 319 | if (path) { 320 | _items = self.queue[depth].data[cfg.columnKeys.items][index][cfg.columnKeys.items]; 321 | } 322 | if (_items && _items.length > 0) {} else { 323 | jQuery(this).removeClass("hover"); 324 | } 325 | }); 326 | 327 | // is Root 328 | if (depth == 0) { 329 | if (data.direction) activeMenu.addClass("direction-" + data.direction); 330 | onStateChanged.call(this, null, { 331 | self: this, 332 | items: items, 333 | parent: function (path) { 334 | if (!path) return false; 335 | var item = null; 336 | try { 337 | item = Function("", "return this.config.items[" + path.substring(5).replace(/\./g, '].items[') + "];").call(self); 338 | } catch (e) {} 339 | return item; 340 | }(data['@path']), 341 | state: "popup" 342 | }); 343 | } 344 | 345 | align.call(this, activeMenu, data); 346 | onLoad.call(this, { 347 | self: this, 348 | items: items, 349 | element: activeMenu.get(0) 350 | }); 351 | 352 | data = null; 353 | activeMenu = null; 354 | removed = null; 355 | opt = null; 356 | items = null; 357 | depth = null; 358 | path = null; 359 | 360 | return this; 361 | }; 362 | 363 | var clickItem = function clickItem(opt, e) { 364 | var target = void 0, 365 | item = void 0; 366 | 367 | target = U.findParentNode(e.target, function (target) { 368 | if (target.getAttribute("data-menu-item-index")) { 369 | return true; 370 | } 371 | }); 372 | if (target) { 373 | if (typeof opt === "undefined") opt = {}; 374 | item = function (path) { 375 | if (!path) return false; 376 | var item = void 0; 377 | 378 | try { 379 | item = Function("", "return this[" + path.substring(5).replace(/\./g, '].' + cfg.columnKeys.items + '[') + "];").call(opt.items || cfg.items); 380 | } catch (e) { 381 | console.log(ax5.info.getError("ax5menu", "501", "menuItemClick")); 382 | } 383 | 384 | try { 385 | return item; 386 | } finally { 387 | item = null; 388 | } 389 | }(target.getAttribute("data-menu-item-path")); 390 | 391 | if (!item) return this; 392 | 393 | if (item.check) { 394 | (function (items) { 395 | var setValue = { 396 | 'checkbox': function checkbox(value) { 397 | this.checked = !value; 398 | }, 399 | 'radio': function radio(value) { 400 | var name = this.name; 401 | items.forEach(function (n) { 402 | if (n.check && n.check.type === 'radio' && n.check.name == name) { 403 | n.check.checked = false; 404 | } 405 | }); 406 | this.checked = !value; 407 | } 408 | }; 409 | if (setValue[this.type]) setValue[this.type].call(this, this.checked); 410 | setValue = null; 411 | }).call(item.check, cfg.items); 412 | 413 | if (!cfg.itemClickAndClose) { 414 | self.queue.forEach(function (n) { 415 | n.$target.find('[data-menu-item-index]').each(function () { 416 | var item = n.data[cfg.columnKeys.items][this.getAttribute("data-menu-item-index")]; 417 | if (item.check) { 418 | jQuery(this).find(".item-checkbox-wrap").attr("data-item-checked", item.check.checked); 419 | } 420 | }); 421 | }); 422 | } 423 | } 424 | 425 | if (self.onClick) { 426 | if (self.onClick.call(item, item, opt.param)) { 427 | self.close(); 428 | } 429 | } 430 | if ((!item[cfg.columnKeys.items] || item[cfg.columnKeys.items].length == 0) && cfg.itemClickAndClose) self.close(); 431 | } else { 432 | self.close(); 433 | } 434 | 435 | target = null; 436 | item = null; 437 | return this; 438 | }; 439 | 440 | var align = function align(activeMenu, data) { 441 | var $window = jQuery(window), 442 | $document = jQuery(document), 443 | wh = cfg.position == "fixed" ? $window.height() : $document.height(), 444 | ww = $window.width(), 445 | h = activeMenu.outerHeight(), 446 | w = activeMenu.outerWidth(), 447 | l = data.left, 448 | t = data.top, 449 | position = cfg.position || "fixed"; 450 | 451 | if (l + w > ww) { 452 | if (data['@parent']) { 453 | l = data['@parent'].left - w + cfg.menuBodyPadding; 454 | } else { 455 | l = ww - w; 456 | } 457 | } 458 | 459 | if (t + h > wh) { 460 | t = wh - h; 461 | } 462 | 463 | activeMenu.css({ left: l, top: t, position: position }); 464 | 465 | activeMenu = null; 466 | data = null; 467 | $window = null; 468 | $document = null; 469 | wh = null; 470 | ww = null; 471 | h = null; 472 | w = null; 473 | l = null; 474 | t = null; 475 | position = null; 476 | return this; 477 | }; 478 | 479 | /// private end 480 | 481 | this.init = function () { 482 | /** 483 | * config에 선언된 이벤트 함수들을 this로 이동시켜 주어 나중에 인스턴스.on... 으로 처리 가능 하도록 변경 484 | */ 485 | this.onStateChanged = cfg.onStateChanged; 486 | this.onClick = cfg.onClick; 487 | this.onLoad = cfg.onLoad; 488 | 489 | onStateChanged.call(this, null, { 490 | self: this, 491 | state: "init" 492 | }); 493 | }; 494 | 495 | /** 496 | * @method ax5.ui.menu.popup 497 | * @param {Event|Object} e - Event or Object 498 | * @param {Object} [opt] 499 | * @param {String} [opt.theme] 500 | * @param {Function} [opt.filter] 501 | * @returns {ax5.ui.menu} this 502 | */ 503 | this.popup = function () { 504 | 505 | var getOption = { 506 | 'event': function event(e, opt) { 507 | //var xOffset = Math.max(document.documentElement.scrollLeft, document.body.scrollLeft); 508 | //var yOffset = Math.max(document.documentElement.scrollTop, document.body.scrollTop); 509 | //console.log(e.pageY); 510 | 511 | e = { 512 | left: e.clientX, 513 | top: cfg.position == "fixed" ? e.clientY : e.pageY, 514 | width: cfg.width, 515 | theme: cfg.theme 516 | }; 517 | 518 | e.left -= 5; 519 | e.top -= 5; 520 | 521 | if (cfg.offset) { 522 | if (cfg.offset.left) e.left += cfg.offset.left; 523 | if (cfg.offset.top) e.top += cfg.offset.top; 524 | } 525 | opt = jQuery.extend(true, e, opt); 526 | 527 | try { 528 | return opt; 529 | } finally { 530 | e = null; 531 | //opt = null; 532 | } 533 | }, 534 | 'object': function object(e, opt) { 535 | e = { 536 | left: e.left, 537 | top: e.top, 538 | width: e.width || cfg.width, 539 | theme: e.theme || cfg.theme 540 | }; 541 | 542 | if (cfg.offset) { 543 | if (cfg.offset.left) e.left += cfg.offset.left; 544 | if (cfg.offset.top) e.top += cfg.offset.top; 545 | } 546 | 547 | opt = jQuery.extend(true, e, opt); 548 | 549 | try { 550 | return opt; 551 | } finally { 552 | e = null; 553 | //opt = null; 554 | } 555 | } 556 | }, 557 | updateTheme = function updateTheme(theme) { 558 | if (theme) cfg.theme = theme; 559 | }; 560 | 561 | return function (e, opt) { 562 | 563 | if (!e) return this; 564 | opt = getOption[typeof e.clientX == "undefined" ? "object" : "event"].call(this, e, opt); 565 | updateTheme(opt.theme); 566 | 567 | var items = [].concat(cfg.items), 568 | _filteringItem = void 0; 569 | opt.items = items; 570 | 571 | if (opt.filter) { 572 | _filteringItem = function filteringItem(_items) { 573 | var arr = []; 574 | _items.forEach(function (n) { 575 | if (n.items && n.items.length > 0) { 576 | n.items = _filteringItem(n.items); 577 | } 578 | if (opt.filter.call(n)) { 579 | arr.push(n); 580 | } 581 | }); 582 | return arr; 583 | }; 584 | opt.items = items = _filteringItem(items); 585 | } 586 | 587 | if (items.length) { 588 | appEventAttach.call(this, false); 589 | popup.call(this, opt, items, 0); // 0 is seq of queue 590 | 591 | if (this.popupEventAttachTimer) clearTimeout(this.popupEventAttachTimer); 592 | this.popupEventAttachTimer = setTimeout(function () { 593 | appEventAttach.call(this, true, opt); // 이벤트 연결 594 | }.bind(this), 500); 595 | } 596 | 597 | e = null; 598 | return this; 599 | }; 600 | }(); 601 | 602 | /** 603 | * @method ax5.ui.menu.attach 604 | * @param {Element|jQueryObject} el 605 | * @returns {ax5.ui.menu} this 606 | */ 607 | this.attach = function () { 608 | 609 | var getOption = { 610 | 'object': function object(e, opt) { 611 | e = { 612 | left: e.left, 613 | top: e.top, 614 | width: e.width || cfg.width, 615 | theme: e.theme || cfg.theme, 616 | direction: e.direction || cfg.direction 617 | }; 618 | opt = jQuery.extend(true, opt, e); 619 | 620 | try { 621 | return opt; 622 | } finally { 623 | e = null; 624 | opt = null; 625 | } 626 | } 627 | }; 628 | 629 | var popUpChildMenu = function popUpChildMenu(target, opt, eType) { 630 | var $target = jQuery(target), 631 | offset = $target.offset(), 632 | height = $target.outerHeight(), 633 | index = Number(target.getAttribute("data-menu-item-index")), 634 | scrollTop = cfg.position == "fixed" ? jQuery(document).scrollTop() : 0; 635 | 636 | if (cfg.items && cfg.items[index][cfg.columnKeys.items] && cfg.items[index][cfg.columnKeys.items].length) { 637 | 638 | if (self.menuBar.openedIndex == index) { 639 | if (eType == "click") self.close(); 640 | return false; 641 | } 642 | 643 | self.menuBar.target.find('[data-menu-item-index]').removeClass("hover"); 644 | self.menuBar.opened = true; 645 | self.menuBar.openedIndex = index; 646 | 647 | $target.attr("data-menu-item-opened", "true"); 648 | $target.addClass("hover"); 649 | 650 | if (cfg.offset) { 651 | if (cfg.offset.left) offset.left += cfg.offset.left; 652 | if (cfg.offset.top) offset.top += cfg.offset.top; 653 | } 654 | 655 | opt = getOption["object"].call(this, { left: offset.left, top: offset.top + height - scrollTop }, opt); 656 | 657 | popup.call(self, opt, cfg.items[index][cfg.columnKeys.items], 0, 'root.' + target.getAttribute("data-menu-item-index")); // 0 is seq of queue 658 | appEventAttach.call(self, true, {}); // 이벤트 연결 659 | } 660 | 661 | target = null; 662 | opt = null; 663 | $target = null; 664 | offset = null; 665 | height = null; 666 | index = null; 667 | scrollTop = null; 668 | }; 669 | var clickParentMenu = function clickParentMenu(target, opt, eType) { 670 | var $target = jQuery(target), 671 | offset = $target.offset(), 672 | height = $target.outerHeight(), 673 | index = Number(target.getAttribute("data-menu-item-index")), 674 | scrollTop = cfg.position == "fixed" ? jQuery(document).scrollTop() : 0; 675 | if (cfg.items && (!cfg.items[index][cfg.columnKeys.items] || cfg.items[index][cfg.columnKeys.items].length == 0)) { 676 | if (self.onClick) { 677 | self.onClick.call(cfg.items[index], cfg.items[index]); 678 | } 679 | } 680 | }; 681 | 682 | return function (el, opt) { 683 | var data = {}, 684 | items = cfg.items, 685 | activeMenu; 686 | 687 | if (typeof opt === "undefined") opt = {}; 688 | 689 | data.theme = opt.theme || cfg.theme; 690 | data.cfg = { 691 | icons: jQuery.extend({}, cfg.icons), 692 | iconWidth: opt.iconWidth || cfg.iconWidth, 693 | acceleratorWidth: opt.acceleratorWidth || cfg.acceleratorWidth 694 | }; 695 | 696 | items.forEach(function (n) { 697 | if (n.html || n.divide) { 698 | n['@isMenu'] = false; 699 | if (n.html) { 700 | n['@html'] = n.html.call({ 701 | item: n, 702 | config: cfg, 703 | opt: opt 704 | }); 705 | } 706 | } else { 707 | n['@isMenu'] = true; 708 | } 709 | }); 710 | 711 | data[cfg.columnKeys.items] = items; 712 | 713 | activeMenu = jQuery(MENU.tmpl.get.call(this, "tmplMenubar", data, cfg.columnKeys)); 714 | self.menuBar = { 715 | target: jQuery(el), 716 | opened: false 717 | }; 718 | self.menuBar.target.html(activeMenu); 719 | 720 | // click, mouseover 721 | self.menuBar.target.bind("click", function (e) { 722 | if (!e) return this; 723 | var target = U.findParentNode(e.target, function (target) { 724 | if (target.getAttribute("data-menu-item-index")) { 725 | return true; 726 | } 727 | }); 728 | if (target) { 729 | clickParentMenu(target, opt, "click"); 730 | popUpChildMenu(target, opt, "click"); 731 | } 732 | 733 | target = null; 734 | }); 735 | self.menuBar.target.bind("mouseover", function (e) { 736 | if (!self.menuBar.opened) return false; 737 | var target = U.findParentNode(e.target, function (target) { 738 | if (target.getAttribute("data-menu-item-index")) { 739 | return true; 740 | } 741 | }); 742 | if (target) popUpChildMenu(target, opt, "mouseover"); 743 | 744 | target = null; 745 | }); 746 | 747 | el = null; 748 | opt = null; 749 | data = null; 750 | items = null; 751 | activeMenu = null; 752 | 753 | return this; 754 | }; 755 | }(); 756 | 757 | /** 758 | * @method ax5.ui.menu.close 759 | * @returns {ax5.ui.menu} this 760 | */ 761 | this.close = function () { 762 | 763 | if (self.menuBar && self.menuBar.target) { 764 | self.menuBar.target.find('[data-menu-item-index]').removeClass("hover"); 765 | self.menuBar.opened = false; 766 | self.menuBar.openedIndex = null; 767 | } 768 | 769 | appEventAttach.call(this, false); // 이벤트 제거 770 | 771 | this.queue.forEach(function (n) { 772 | n.$target.remove(); 773 | }); 774 | this.queue = []; 775 | 776 | onStateChanged.call(this, null, { 777 | self: this, 778 | state: "close" 779 | }); 780 | 781 | return this; 782 | }; 783 | 784 | /** 785 | * @method ax5.ui.menu.getCheckValue 786 | * @returns {Object} statusCheckItem 787 | */ 788 | this.getCheckValue = function () { 789 | var checkItems = {}, 790 | _collectItem = function collectItem(items) { 791 | var i = items.length; 792 | while (i--) { 793 | if (items[i].check && items[i].check.checked) { 794 | if (!checkItems[items[i].check.name]) checkItems[items[i].check.name] = items[i].check.value;else { 795 | if (U.isString(checkItems[items[i].check.name])) checkItems[items[i].check.name] = [checkItems[items[i].check.name]]; 796 | checkItems[items[i].check.name].push(items[i].check.value); 797 | } 798 | } 799 | if (items[i].items && items[i].items.length > 0) _collectItem(items[i].items); 800 | } 801 | }; 802 | 803 | _collectItem(cfg.items); 804 | 805 | try { 806 | return checkItems; 807 | } finally { 808 | checkItems = null; 809 | _collectItem = null; 810 | } 811 | }; 812 | 813 | // 클래스 생성자 814 | this.main = function () { 815 | 816 | UI.menu_instance = UI.menu_instance || []; 817 | UI.menu_instance.push(this); 818 | 819 | if (arguments && U.isObject(arguments[0])) { 820 | this.setConfig(arguments[0]); 821 | } 822 | }.apply(this, arguments); 823 | }; 824 | }()); 825 | 826 | MENU = ax5.ui.menu; 827 | })(); 828 | 829 | // todo : menu 드랍다운 아이콘 설정 기능 추가 830 | // ax5.ui.menu.tmpl 831 | (function () { 832 | var MENU = ax5.ui.menu; 833 | 834 | var tmpl = function tmpl(columnKeys) { 835 | return "\n
\n
\n {{#" + columnKeys.items + "}}\n {{^@isMenu}}\n {{#divide}}\n
\n {{/divide}}\n {{#html}}\n
{{{@html}}}
\n {{/html}}\n {{/@isMenu}}\n {{#@isMenu}}\n
\n \n {{#check}}\n \n {{/check}}\n {{^check}}\n \n {{/check}}\n \n {{#icon}}\n {{{.}}}\n {{/icon}}\n {{{" + columnKeys.label + "}}}\n {{#accelerator}}\n {{.}}\n {{/accelerator}}\n {{#@hasChild}}\n {{{cfg.icons.arrow}}}\n {{/@hasChild}}\n
\n {{/@isMenu}}\n\n {{/" + columnKeys.items + "}}\n
\n
\n
\n "; 836 | }; 837 | var tmplMenubar = function tmplMenubar(columnKeys) { 838 | return "\n
\n
\n {{#" + columnKeys.items + "}}\n {{^@isMenu}}\n {{#divide}}\n
\n {{/divide}}\n {{#html}}\n
{{{@html}}}
\n {{/html}}\n {{/@isMenu}}\n {{#@isMenu}}\n
\n {{#icon}}\n {{{.}}}\n {{/icon}}\n {{{" + columnKeys.label + "}}}\n
\n {{/@isMenu}}\n {{/" + columnKeys.items + "}}\n
\n
\n "; 839 | }; 840 | 841 | MENU.tmpl = { 842 | "tmpl": tmpl, 843 | "tmplMenubar": tmplMenubar, 844 | 845 | get: function get(tmplName, data, columnKeys) { 846 | return ax5.mustache.render(MENU.tmpl[tmplName].call(this, columnKeys), data); 847 | } 848 | }; 849 | })(); -------------------------------------------------------------------------------- /dist/ax5menu.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["ax5menu.js","ax5menu-tmpl.js"],"names":["MENU","UI","ax5","ui","U","util","addClass","className","self","this","cfg","instanceId","getGuid","config","theme","iconWidth","acceleratorWidth","menuBodyPadding","offset","left","top","position","animateTime","items","itemClickAndClose","columnKeys","label","openTimer","closeTimer","queue","menuBar","state","undefined","appEventAttach","active","opt","jQuery","document","body","off","on","clickItem","bind","window","e","which","info","eventKeys","ESC","close","onStateChanged","opts","that","call","onLoad","popup","depth","path","data","activeMenu","removed","icons","extend","forEach","n","html","divide","item","length","tmpl","get","append","splice","$target","remove","push","find","getAttribute","index","$this","scrollTop","childOpt","_items","_activeMenu","removeClass","attr","@parent","width","outerWidth","height","outerHeight","Number","direction","parent","Function","substring","replace","align","element","target","findParentNode","console","log","getError","check","setValue","checkbox","value","checked","radio","name","type","each","onClick","param","$window","$document","wh","ww","h","w","l","t","css","init","getOption","event","clientX","clientY","pageY","object","updateTheme","concat","filteringItem","filter","arr","popupEventAttachTimer","clearTimeout","setTimeout","attach","popUpChildMenu","eType","openedIndex","opened","clickParentMenu","el","getCheckValue","checkItems","collectItem","i","isString","main","menu_instance","arguments","isObject","setConfig","apply","menu","tmplMenubar","tmplName","mustache","render"],"mappings":"cACA,WACA,GAEAA,GAFAC,EAAAC,IAAAC,GACAC,EAAAF,IAAAG,IAGAJ,GAAAK,UACAC,UAAA,QACA,WA4HA,MAAA,YACA,GAAAC,GAAAC,KACAC,EAAAA,MAEAD,MAAAE,WAAAT,IAAAU,UACAH,KAAAI,QACAC,MAAA,UACAC,UAAA,GACAC,iBAAA,IACAC,gBAAA,EAEAC,QAAAC,KAAA,EAAAC,IAAA,GACAC,SAAA,QACAC,YAAA,IACAC,SACAC,mBAAA,EACAC,YACAC,MAAA,QACAH,MAAA,UAIAd,KAAAkB,UAAA,KACAlB,KAAAmB,WAAA,KACAnB,KAAAoB,SACApB,KAAAqB,WACArB,KAAAsB,MAAAC,OAEAtB,EAAAD,KAAAI,MAEA,IAAAoB,GAAA,SAAAC,EAAAC,GACAD,GACAE,OAAAC,SAAAC,MACAC,IAAA,iBAAA9B,KAAAE,YACA6B,GAAA,iBAAA/B,KAAAE,WAAA8B,EAAAC,KAAAjC,KAAA0B,IAEAC,OAAAO,QACAJ,IAAA,mBAAA9B,KAAAE,YACA6B,GAAA,mBAAA/B,KAAAE,WAAA,SAAAiC,GACAA,EAAAC,OAAA3C,IAAA4C,KAAAC,UAAAC,KACAxC,EAAAyC,UAIAb,OAAAO,QACAH,GAAA,kBAAA/B,KAAAE,YACA6B,GAAA,kBAAA/B,KAAAE,WAAA,SAAAiC,GACApC,EAAAyC,YAIAb,OAAAC,SAAAC,MAAAC,IAAA,iBAAA9B,KAAAE,YACAyB,OAAAO,QAAAJ,IAAA,mBAAA9B,KAAAE,YACAyB,OAAAO,QAAAJ,IAAA,kBAAA9B,KAAAE,cAIAuC,EAAA,SAAAC,EAAAC,GAWA,MAVAD,IAAAA,EAAAD,eACAC,EAAAD,eAAAG,KAAAD,EAAAA,GAEA3C,KAAAyC,gBACAzC,KAAAyC,eAAAG,KAAAD,EAAAA,GAGA5C,EAAAuB,MAAAqB,EAAArB,MACAoB,EAAA,KACAC,EAAA,MACA,GAGAE,EAAA,SAAAF,GAMA,MALA3C,MAAA6C,QACA7C,KAAA6C,OAAAD,KAAAD,EAAAA,GAGAA,EAAA,MACA,GAGAG,EAAA,QAAAA,GAAApB,EAAAZ,EAAAiC,EAAAC,GACA,GAAAC,GAAAvB,EACAwB,EAAAA,OACAC,EAAAA,MA6JA,OA1JAF,GAAA5C,MAAAqB,EAAArB,OAAAJ,EAAAI,MACA4C,EAAAhD,KACAmD,MAAAzB,OAAA0B,UAAApD,EAAAmD,OACA9C,UAAAoB,EAAApB,WAAAL,EAAAK,UACAC,iBAAAmB,EAAAnB,kBAAAN,EAAAM,kBAGAO,EAAAwC,QAAA,SAAAC,GACAA,EAAAC,MAAAD,EAAAE,QACAF,EAAA,YAAA,EACAA,EAAAC,OACAD,EAAA,SAAAA,EAAAC,KAAAZ,MACAc,KAAAH,EACAnD,OAAAH,EACAyB,IAAAA,MAKA6B,EAAA,YAAA,IAIAN,EAAAhD,EAAAe,WAAAF,OAAAA,EACAmC,EAAA,UAAAF,EACAE,EAAA,SAAAD,GAAA,OACAC,EAAA,aAAA,WACA,MAAAjD,MAAAC,EAAAe,WAAAF,QAAAd,KAAAC,EAAAe,WAAAF,OAAA6C,OAAA,GAEAT,EAAAvB,OAAApC,EAAAqE,KAAAC,IAAAjB,KAAA5C,KAAA,OAAAiD,EAAAhD,EAAAe,aACAW,OAAAC,SAAAC,MAAAiC,OAAAZ,GAIAC,EAAAnD,KAAAoB,MAAA2C,OAAAhB,GACAI,EAAAG,QAAA,SAAAC,GACAA,EAAAS,QAAAC,WAGAjE,KAAAoB,MAAA8C,MACAF,QAAAd,EACAD,KAAAtB,OAAA0B,UAAAJ,KAGAC,EAAAiB,KAAA,0BAAAlC,KAAA,YAAA,WACA,GAAAc,GAAA/C,KAAAoE,aAAA,wBACAC,EAAArE,KAAAoE,aAAA,wBACApB,EAAAhD,KAAAoE,aAAA,uBACAE,EAAAA,OACA7D,EAAAA,OACA8D,EAAAA,OACAC,EAAAA,OACAC,EAAAA,OACAC,EAAAA,MAEA,OAAA3B,GAAA,mBAAAA,KACA0B,EAAA1E,EAAAqB,MAAA2B,GAAAE,KAAAhD,EAAAe,WAAAF,OAAAuD,GAAApE,EAAAe,WAAAF,OACA4D,EAAA3E,EAAAqB,MAAA2B,GAAAiB,QACAU,EAAAP,KAAA,0BAAAQ,YAAA,SACAhD,OAAA3B,MAAAH,SAAA,SAEA6E,EAAAE,KAAA,kCAAAP,IACAK,EAAAE,KAAA,gCAAAP,GAEAI,GAAAA,EAAAd,OAAA,GAEAW,EAAA3C,OAAA3B,MACAS,EAAA6D,EAAA7D,SACA8D,EAAA,SAAAtE,EAAAW,SAAAe,OAAAC,UAAA2C,YAAA,EACAC,GACAK,WACAnE,KAAAD,EAAAC,KACAC,IAAAF,EAAAE,IACAmE,MAAAR,EAAAS,aACAC,OAAAV,EAAAW,eAEAvE,KAAAD,EAAAC,KAAA4D,EAAAS,aAAA9E,EAAAO,gBACAG,IAAAF,EAAAE,IAAAV,EAAAO,gBAAA,EAAA+D,GAGAC,EAAA7C,OAAA0B,QAAA,EAAA3B,EAAA8C,GACA1B,EAAAF,KAAA7C,EAAAyE,EAAAC,EAAAS,OAAAnC,GAAA,EAAAC,IAGAjD,EAAAqB,MAAA2C,OAAAmB,OAAAnC,GAAA,GAAAO,QAAA,SAAAC,GACAA,EAAAS,QAAAC,aAMAlB,EAAA,KACAsB,EAAA,KACArB,EAAA,KACAsB,EAAA,KACA7D,EAAA,KACA8D,EAAA,KACAC,EAAA,KACAC,EAAA,KACAC,EAAA,OAIAxB,EAAAiB,KAAA,0BAAAlC,KAAA,WAAA,WACA,GAAAc,GAAA/C,KAAAoE,aAAA,wBACAC,EAAArE,KAAAoE,aAAA,wBACApB,EAAAhD,KAAAoE,aAAA,uBACAK,EAAAA,MAEAzB,KACAyB,EAAA1E,EAAAqB,MAAA2B,GAAAE,KAAAhD,EAAAe,WAAAF,OAAAuD,GAAApE,EAAAe,WAAAF,QAEA2D,GAAAA,EAAAd,OAAA,GAGAhC,OAAA3B,MAAA2E,YAAA,WAKA,GAAA5B,IACAE,EAAAkC,WAAAjC,EAAArD,SAAA,aAAAoD,EAAAkC,WACA1C,EAAAG,KAAA5C,KAAA,MACAD,KAAAC,KACAc,MAAAA,EACAsE,OAAA,SAAApC,GACA,IAAAA,EAAA,OAAA,CACA,IAAAU,GAAA,IACA,KACAA,EAAA2B,SAAA,GAAA,4BAAArC,EAAAsC,UAAA,GAAAC,QAAA,MAAA,YAAA,MAAA3C,KAAA7C,GACA,MAAAoC,IAGA,MAAAuB,IACAT,EAAA,UACA3B,MAAA,WAIAkE,EAAA5C,KAAA5C,KAAAkD,EAAAD,GACAJ,EAAAD,KAAA5C,MACAD,KAAAC,KACAc,MAAAA,EACA2E,QAAAvC,EAAAW,IAAA,KAGAZ,EAAA,KACAC,EAAA,KACAC,EAAA,KACAzB,EAAA,KACAZ,EAAA,KACAiC,EAAA,KACAC,EAAA,KAEAhD,MAGAgC,EAAA,SAAAN,EAAAS,GACA,GAAAuD,GAAAA,OAAAhC,EAAAA,MAOA,IALAgC,EAAA/F,EAAAgG,eAAAxD,EAAAuD,OAAA,SAAAA,GACA,GAAAA,EAAAtB,aAAA,wBACA,OAAA,IAGA,CAoBA,GAnBA,mBAAA1C,KAAAA,MACAgC,EAAA,SAAAV,GACA,IAAAA,EAAA,OAAA,CACA,IAAAU,GAAAA,MAEA,KACAA,EAAA2B,SAAA,GAAA,eAAArC,EAAAsC,UAAA,GAAAC,QAAA,MAAA,KAAAtF,EAAAe,WAAAF,MAAA,KAAA,MAAA8B,KAAAlB,EAAAZ,OAAAb,EAAAa,OACA,MAAAqB,GACAyD,QAAAC,IAAApG,IAAA4C,KAAAyD,SAAA,UAAA,MAAA,kBAGA,IACA,MAAApC,GADA,QAIAA,EAAA,OAEAgC,EAAAtB,aAAA,yBAEAV,EAAA,MAAA1D,KAEA0D,GAAAqC,QACA,SAAAjF,GACA,GAAAkF,IACAC,SAAA,SAAAC,GACAlG,KAAAmG,SAAAD,GAEAE,MAAA,SAAAF,GACA,GAAAG,GAAArG,KAAAqG,IACAvF,GAAAwC,QAAA,SAAAC,GACAA,EAAAwC,OAAA,UAAAxC,EAAAwC,MAAAO,MAAA/C,EAAAwC,MAAAM,MAAAA,IACA9C,EAAAwC,MAAAI,SAAA,KAGAnG,KAAAmG,SAAAD,GAGAF,GAAAhG,KAAAsG,OAAAN,EAAAhG,KAAAsG,MAAA1D,KAAA5C,KAAAA,KAAAmG,SACAH,EAAA,MACApD,KAAAc,EAAAqC,MAAA9F,EAAAa,OAEAb,EAAAc,mBACAhB,EAAAqB,MAAAkC,QAAA,SAAAC,GACAA,EAAAS,QAAAG,KAAA,0BAAAoC,KAAA,WACA,GAAA7C,GAAAH,EAAAN,KAAAhD,EAAAe,WAAAF,OAAAd,KAAAoE,aAAA,wBACAV,GAAAqC,OACApE,OAAA3B,MAAAmE,KAAA,uBAAAS,KAAA,oBAAAlB,EAAAqC,MAAAI,cAOApG,EAAAyG,SACAzG,EAAAyG,QAAA5D,KAAAc,EAAAA,EAAAhC,EAAA+E,QACA1G,EAAAyC,QAGAkB,EAAAzD,EAAAe,WAAAF,QAAA,GAAA4C,EAAAzD,EAAAe,WAAAF,OAAA6C,SAAA1D,EAAAc,mBAAAhB,EAAAyC,YAGAzC,GAAAyC,OAKA,OAFAkD,GAAA,KACAhC,EAAA,KACA1D,MAGAwF,EAAA,SAAAtC,EAAAD,GACA,GAAAyD,GAAA/E,OAAAO,QACAyE,EAAAhF,OAAAC,UACAgF,EAAA,SAAA3G,EAAAW,SAAA8F,EAAA1B,SAAA2B,EAAA3B,SACA6B,EAAAH,EAAA5B,QACAgC,EAAA5D,EAAA+B,cACA8B,EAAA7D,EAAA6B,aACAiC,EAAA/D,EAAAvC,KACAuG,EAAAhE,EAAAtC,IACAC,EAAAX,EAAAW,UAAA,OA4BA,OA1BAoG,GAAAD,EAAAF,IAEAG,EADA/D,EAAA,WACAA,EAAA,WAAAvC,KAAAqG,EAAA9G,EAAAO,gBAGAqG,EAAAE,GAIAE,EAAAH,EAAAF,IACAK,EAAAL,EAAAE,GAGA5D,EAAAgE,KAAAxG,KAAAsG,EAAArG,IAAAsG,EAAArG,SAAAA,IAEAsC,EAAA,KACAD,EAAA,KACAyD,EAAA,KACAC,EAAA,KACAC,EAAA,KACAC,EAAA,KACAC,EAAA,KACAC,EAAA,KACAC,EAAA,KACAC,EAAA,KACArG,EAAA,KACAZ,KAKAA,MAAAmH,KAAA,WAIAnH,KAAAyC,eAAAxC,EAAAwC,eACAzC,KAAAwG,QAAAvG,EAAAuG,QACAxG,KAAA6C,OAAA5C,EAAA4C,OAEAJ,EAAAG,KAAA5C,KAAA,MACAD,KAAAC,KACAsB,MAAA,UAYAtB,KAAA8C,MAAA,WAEA,GAAAsE,IACAC,MAAA,SAAAlF,EAAAT,GAKAS,GACAzB,KAAAyB,EAAAmF,QACA3G,IAAA,SAAAV,EAAAW,SAAAuB,EAAAoF,QAAApF,EAAAqF,MACA1C,MAAA7E,EAAA6E,MACAzE,MAAAJ,EAAAI,OAGA8B,EAAAzB,MAAA,EACAyB,EAAAxB,KAAA,EAEAV,EAAAQ,SACAR,EAAAQ,OAAAC,OAAAyB,EAAAzB,MAAAT,EAAAQ,OAAAC,MACAT,EAAAQ,OAAAE,MAAAwB,EAAAxB,KAAAV,EAAAQ,OAAAE,MAEAe,EAAAC,OAAA0B,QAAA,EAAAlB,EAAAT,EAEA,KACA,MAAAA,GADA,QAIAS,EAAA,OAIAsF,OAAA,SAAAtF,EAAAT,GACAS,GACAzB,KAAAyB,EAAAzB,KACAC,IAAAwB,EAAAxB,IACAmE,MAAA3C,EAAA2C,OAAA7E,EAAA6E,MACAzE,MAAA8B,EAAA9B,OAAAJ,EAAAI,OAGAJ,EAAAQ,SACAR,EAAAQ,OAAAC,OAAAyB,EAAAzB,MAAAT,EAAAQ,OAAAC,MACAT,EAAAQ,OAAAE,MAAAwB,EAAAxB,KAAAV,EAAAQ,OAAAE,MAGAe,EAAAC,OAAA0B,QAAA,EAAAlB,EAAAT,EAEA,KACA,MAAAA,GADA,QAIAS,EAAA,QAKAuF,EAAA,SAAArH,GACAA,IAAAJ,EAAAI,MAAAA,GAGA,OAAA,UAAA8B,EAAAT,GAEA,IAAAS,EAAA,MAAAnC,KACA0B,GAAA0F,EAAA,mBAAAjF,GAAAmF,QAAA,SAAA,SAAA1E,KAAA5C,KAAAmC,EAAAT,GACAgG,EAAAhG,EAAArB,MAEA,IAAAS,MAAA6G,OAAA1H,EAAAa,OACA8G,EAAAA,MA8BA,OA7BAlG,GAAAZ,MAAAA,EAEAY,EAAAmG,SACAD,EAAA,SAAAnD,GACA,GAAAqD,KASA,OARArD,GAAAnB,QAAA,SAAAC,GACAA,EAAAzC,OAAAyC,EAAAzC,MAAA6C,OAAA,IACAJ,EAAAzC,MAAA8G,EAAArE,EAAAzC,QAEAY,EAAAmG,OAAAjF,KAAAW,IACAuE,EAAA5D,KAAAX,KAGAuE,GAEApG,EAAAZ,MAAAA,EAAA8G,EAAA9G,IAGAA,EAAA6C,SACAnC,EAAAoB,KAAA5C,MAAA,GACA8C,EAAAF,KAAA5C,KAAA0B,EAAAZ,EAAA,GAEAd,KAAA+H,uBAAAC,aAAAhI,KAAA+H,uBACA/H,KAAA+H,sBAAAE,WAAA,WACAzG,EAAAoB,KAAA5C,MAAA,EAAA0B,IACAO,KAAAjC,MAAA,MAGAmC,EAAA,KACAnC,SASAA,KAAAkI,OAAA,WAEA,GAAAd,IACAK,OAAA,SAAAtF,EAAAT,GACAS,GACAzB,KAAAyB,EAAAzB,KACAC,IAAAwB,EAAAxB,IACAmE,MAAA3C,EAAA2C,OAAA7E,EAAA6E,MACAzE,MAAA8B,EAAA9B,OAAAJ,EAAAI,MACA8E,UAAAhD,EAAAgD,WAAAlF,EAAAkF,WAEAzD,EAAAC,OAAA0B,QAAA,EAAA3B,EAAAS,EAEA,KACA,MAAAT,GADA,QAIAS,EAAA,KACAT,EAAA,QAKAyG,EAAA,SAAAzC,EAAAhE,EAAA0G,GACA,GACApE,GAAArC,OAAA+D,GACAjF,EAAAuD,EAAAvD,SACAuE,EAAAhB,EAAAiB,cACAZ,EAAAa,OAAAQ,EAAAtB,aAAA,yBACAG,EAAA,SAAAtE,EAAAW,SAAAe,OAAAC,UAAA2C,YAAA,CAEA,IAAAtE,EAAAa,OAAAb,EAAAa,MAAAuD,GAAApE,EAAAe,WAAAF,QAAAb,EAAAa,MAAAuD,GAAApE,EAAAe,WAAAF,OAAA6C,OAAA,CAEA,GAAA5D,EAAAsB,QAAAgH,aAAAhE,EAEA,MADA,SAAA+D,GAAArI,EAAAyC,SACA,CAGAzC,GAAAsB,QAAAqE,OAAAvB,KAAA,0BAAAQ,YAAA,SACA5E,EAAAsB,QAAAiH,QAAA,EACAvI,EAAAsB,QAAAgH,YAAAhE,EAEAL,EAAAY,KAAA,wBAAA,QACAZ,EAAAnE,SAAA,SAEAI,EAAAQ,SACAR,EAAAQ,OAAAC,OAAAD,EAAAC,MAAAT,EAAAQ,OAAAC,MACAT,EAAAQ,OAAAE,MAAAF,EAAAE,KAAAV,EAAAQ,OAAAE,MAGAe,EAAA0F,EAAA,OAAAxE,KAAA5C,MAAAU,KAAAD,EAAAC,KAAAC,IAAAF,EAAAE,IAAAqE,EAAAT,GAAA7C,GAEAoB,EAAAF,KAAA7C,EAAA2B,EAAAzB,EAAAa,MAAAuD,GAAApE,EAAAe,WAAAF,OAAA,EAAA,QAAA4E,EAAAtB,aAAA,yBACA5C,EAAAoB,KAAA7C,GAAA,MAGA2F,EAAA,KACAhE,EAAA,KACAsC,EAAA,KACAvD,EAAA,KACAuE,EAAA,KACAX,EAAA,KACAE,EAAA,MAEAgE,EAAA,SAAA7C,EAAAhE,EAAA0G,GACA,GACApE,GAAArC,OAAA+D,GAGArB,GAFAL,EAAAvD,SACAuD,EAAAiB,cACAC,OAAAQ,EAAAtB,aAAA,yBACA,UAAAnE,EAAAW,SAAAe,OAAAC,UAAA2C,YAAA,GACAtE,EAAAa,OAAAb,EAAAa,MAAAuD,GAAApE,EAAAe,WAAAF,QAAA,GAAAb,EAAAa,MAAAuD,GAAApE,EAAAe,WAAAF,OAAA6C,QACA5D,EAAAyG,SACAzG,EAAAyG,QAAA5D,KAAA3C,EAAAa,MAAAuD,GAAApE,EAAAa,MAAAuD,IAKA,OAAA,UAAAmE,EAAA9G,GACA,GAGAwB,GAFAD,KACAnC,EAAAb,EAAAa,KAsEA,OAnEA,mBAAAY,KAAAA,MAEAuB,EAAA5C,MAAAqB,EAAArB,OAAAJ,EAAAI,MACA4C,EAAAhD,KACAmD,MAAAzB,OAAA0B,UAAApD,EAAAmD,OACA9C,UAAAoB,EAAApB,WAAAL,EAAAK,UACAC,iBAAAmB,EAAAnB,kBAAAN,EAAAM,kBAGAO,EAAAwC,QAAA,SAAAC,GACAA,EAAAC,MAAAD,EAAAE,QACAF,EAAA,YAAA,EACAA,EAAAC,OACAD,EAAA,SAAAA,EAAAC,KAAAZ,MACAc,KAAAH,EACAnD,OAAAH,EACAyB,IAAAA,MAKA6B,EAAA,YAAA,IAIAN,EAAAhD,EAAAe,WAAAF,OAAAA,EAEAoC,EAAAvB,OAAApC,EAAAqE,KAAAC,IAAAjB,KAAA5C,KAAA,cAAAiD,EAAAhD,EAAAe,aACAjB,EAAAsB,SACAqE,OAAA/D,OAAA6G,GACAF,QAAA,GAEAvI,EAAAsB,QAAAqE,OAAAlC,KAAAN,GAGAnD,EAAAsB,QAAAqE,OAAAzD,KAAA,QAAA,SAAAE,GACA,IAAAA,EAAA,MAAAnC,KACA,IAAA0F,GAAA/F,EAAAgG,eAAAxD,EAAAuD,OAAA,SAAAA,GACA,GAAAA,EAAAtB,aAAA,wBACA,OAAA,GAGAsB,KACA6C,EAAA7C,EAAAhE,EAAA,SACAyG,EAAAzC,EAAAhE,EAAA,UAGAgE,EAAA,OAEA3F,EAAAsB,QAAAqE,OAAAzD,KAAA,YAAA,SAAAE,GACA,IAAApC,EAAAsB,QAAAiH,OAAA,OAAA,CACA,IAAA5C,GAAA/F,EAAAgG,eAAAxD,EAAAuD,OAAA,SAAAA,GACA,GAAAA,EAAAtB,aAAA,wBACA,OAAA,GAGAsB,IAAAyC,EAAAzC,EAAAhE,EAAA,aAEAgE,EAAA,OAGA8C,EAAA,KACA9G,EAAA,KACAuB,EAAA,KACAnC,EAAA,KACAoC,EAAA,KAEAlD,SAQAA,KAAAwC,MAAA,WAoBA,MAlBAzC,GAAAsB,SAAAtB,EAAAsB,QAAAqE,SACA3F,EAAAsB,QAAAqE,OAAAvB,KAAA,0BAAAQ,YAAA,SACA5E,EAAAsB,QAAAiH,QAAA,EACAvI,EAAAsB,QAAAgH,YAAA,MAGA7G,EAAAoB,KAAA5C,MAAA,GAEAA,KAAAoB,MAAAkC,QAAA,SAAAC,GACAA,EAAAS,QAAAC,WAEAjE,KAAAoB,SAEAqB,EAAAG,KAAA5C,KAAA,MACAD,KAAAC,KACAsB,MAAA,UAGAtB,MAOAA,KAAAyI,cAAA,WACA,GAAAC,MACAC,EAAA,SAAA7H,GAEA,IADA,GAAA8H,GAAA9H,EAAA6C,OACAiF,KACA9H,EAAA8H,GAAA7C,OAAAjF,EAAA8H,GAAA7C,MAAAI,UACAuC,EAAA5H,EAAA8H,GAAA7C,MAAAM,OAEA1G,EAAAkJ,SAAAH,EAAA5H,EAAA8H,GAAA7C,MAAAM,SAAAqC,EAAA5H,EAAA8H,GAAA7C,MAAAM,OAAAqC,EAAA5H,EAAA8H,GAAA7C,MAAAM,QACAqC,EAAA5H,EAAA8H,GAAA7C,MAAAM,MAAAnC,KAAApD,EAAA8H,GAAA7C,MAAAG,QAHAwC,EAAA5H,EAAA8H,GAAA7C,MAAAM,MAAAvF,EAAA8H,GAAA7C,MAAAG,OAMApF,EAAA8H,GAAA9H,OAAAA,EAAA8H,GAAA9H,MAAA6C,OAAA,GAAAgF,EAAA7H,EAAA8H,GAAA9H,OAIA6H,GAAA1I,EAAAa,MAEA,KACA,MAAA4H,GADA,QAIAA,EAAA,KACAC,EAAA,OAKA3I,KAAA8I,KAAA,WAEAtJ,EAAAuJ,cAAAvJ,EAAAuJ,kBACAvJ,EAAAuJ,cAAA7E,KAAAlE,MAEAgJ,WAAArJ,EAAAsJ,SAAAD,UAAA,KACAhJ,KAAAkJ,UAAAF,UAAA,KAEAG,MAAAnJ,KAAAgJ,gBAIAzJ,EAAAE,IAAAC,GAAA0J,QCh1BA,WACA,GAAA7J,GAAAE,IAAAC,GAAA0J,KAEAxF,EAAA,SAAA5C,GACA,MAAA,4JAGAA,EAAAF,MAHA,s0CAyBAE,EAAAC,MAzBA,whBAmCAD,EAAAF,MAnCA,mGAyCAuI,EAAA,SAAArI,GACA,MAAA,gHAGAA,EAAAF,MAHA,myBAiBAE,EAAAC,MAjBA,gGAoBAD,EAAAF,MApBA,mDA0BAvB,GAAAqE,MACAA,KAAAA,EACAyF,YAAAA,EAEAxF,IAAA,SAAAyF,EAAArG,EAAAjC,GACA,MAAAvB,KAAA8J,SAAAC,OAAAjK,EAAAqE,KAAA0F,GAAA1G,KAAA5C,KAAAgB,GAAAiC","file":"ax5menu.min.js","sourcesContent":["// ax5.ui.menu\n(function () {\n var UI = ax5.ui;\n var U = ax5.util;\n var MENU;\n\n UI.addClass({\n className: \"menu\"\n }, (function () {\n /**\n * @class ax5.ui.menu\n * @classdesc\n * @author tom@axisj.com\n * @example\n * ```js\n * var menu = new ax5.ui.menu({\n * theme: 'primary',\n * iconWidth: 20,\n * acceleratorWidth: 100,\n * itemClickAndClose: false,\n * icons: {\n * 'arrow': ''\n * },\n * columnKeys: {\n * label: 'name',\n * items: 'chidren'\n * },\n * items: [\n * {\n * icon: '',\n * name: \"Menu Parent 0\",\n * chidren: [\n * {\n * check: {\n * type: 'checkbox',\n * name: 'A',\n * value: '0',\n * checked: false\n * },\n * name: \"Menu Z\",\n * data: {},\n * role: \"\",\n * accelerator: \"CmdOrCtrl+Z\"\n * },\n * {\n * check: {\n * type: 'checkbox',\n * name: 'A',\n * value: '1',\n * checked: true\n * },\n * name: \"Menu A\",\n * data: {},\n * role: \"\"\n * }\n * ],\n * filterType: \"A\"\n * },\n * {\n * divide: true,\n * filterType: \"A\"\n * },\n * {\n * icon: '',\n * name: \"Menu Parent 1\",\n * chidren: [\n * {\n * name: \"Menu Z\",\n * data: {},\n * role: \"\",\n * chidren: [\n * {\n * name: \"Menu Z\",\n * data: {},\n * role: \"\"\n * },\n * {\n * name: \"Menu A\",\n * data: {},\n * role: \"\"\n * }\n * ]\n * },\n * {\n * name: \"Menu A\",\n * data: {},\n * role: \"\"\n * }\n * ],\n * filterType: \"A\"\n * },\n * {\n * check: {\n * type: 'radio',\n * name: 'radioName',\n * value: '1',\n * checked: false\n * },\n * icon: '',\n * name: \"Menu Parent 2\"\n * },\n * {\n * check: {\n * type: 'radio',\n * name: 'radioName',\n * value: '2',\n * checked: false\n * },\n * name: \"Menu Parent 3\"\n * },\n * {\n * check: {\n * type: 'radio',\n * name: 'radioName',\n * value: '3',\n * checked: false\n * },\n * name: \"Menu Parent 4\"\n * },\n * {divide: true},\n * {\n * html: function () {\n * return '
' +\n * ' ' +\n * '' +\n * '
';\n * }\n * }\n * ]\n * });\n * ```\n */\n return function () {\n let self = this,\n cfg;\n\n this.instanceId = ax5.getGuid();\n this.config = {\n theme: \"default\",\n iconWidth: 22,\n acceleratorWidth: 100,\n menuBodyPadding: 5,\n //direction: \"top\", // top|bottom\n offset: {left: 0, top: 0},\n position: \"fixed\",\n animateTime: 250,\n items: [],\n itemClickAndClose: true,\n columnKeys: {\n label: 'label',\n items: 'items'\n }\n };\n\n this.openTimer = null;\n this.closeTimer = null;\n this.queue = [];\n this.menuBar = {};\n this.state = undefined;\n\n cfg = this.config;\n\n const appEventAttach = function (active, opt) {\n if (active) {\n jQuery(document.body)\n .off(\"click.ax5menu-\" + this.instanceId)\n .on(\"click.ax5menu-\" + this.instanceId, clickItem.bind(this, opt));\n\n jQuery(window)\n .off(\"keydown.ax5menu-\" + this.instanceId)\n .on(\"keydown.ax5menu-\" + this.instanceId, function (e) {\n if (e.which == ax5.info.eventKeys.ESC) {\n self.close();\n }\n });\n\n jQuery(window)\n .on(\"resize.ax5menu-\" + this.instanceId)\n .on(\"resize.ax5menu-\" + this.instanceId, function (e) {\n self.close();\n });\n }\n else {\n jQuery(document.body).off(\"click.ax5menu-\" + this.instanceId);\n jQuery(window).off(\"keydown.ax5menu-\" + this.instanceId);\n jQuery(window).off(\"resize.ax5menu-\" + this.instanceId);\n }\n };\n \n const onStateChanged = function (opts, that) {\n if (opts && opts.onStateChanged) {\n opts.onStateChanged.call(that, that);\n }\n else if (this.onStateChanged) {\n this.onStateChanged.call(that, that);\n }\n\n self.state = that.state;\n opts = null;\n that = null;\n return true;\n };\n \n const onLoad = function (that) {\n if (this.onLoad) {\n this.onLoad.call(that, that);\n }\n\n that = null;\n return true;\n };\n \n const popup = function (opt, items, depth, path) {\n let data = opt,\n activeMenu,\n removed\n ;\n\n data.theme = opt.theme || cfg.theme;\n data.cfg = {\n icons: jQuery.extend({}, cfg.icons),\n iconWidth: opt.iconWidth || cfg.iconWidth,\n acceleratorWidth: opt.acceleratorWidth || cfg.acceleratorWidth\n };\n\n items.forEach(function (n) {\n if (n.html || n.divide) {\n n['@isMenu'] = false;\n if (n.html) {\n n['@html'] = n.html.call({\n item: n,\n config: cfg,\n opt: opt\n });\n }\n }\n else {\n n['@isMenu'] = true;\n }\n });\n\n data[cfg.columnKeys.items] = items;\n data['@depth'] = depth;\n data['@path'] = path || \"root\";\n data['@hasChild'] = function () {\n return this[cfg.columnKeys.items] && this[cfg.columnKeys.items].length > 0;\n };\n activeMenu = jQuery(MENU.tmpl.get.call(this, \"tmpl\", data, cfg.columnKeys));\n jQuery(document.body).append(activeMenu);\n\n // remove queue\n\n removed = this.queue.splice(depth);\n removed.forEach(function (n) {\n n.$target.remove();\n });\n\n this.queue.push({\n '$target': activeMenu,\n 'data': jQuery.extend({}, data)\n });\n\n activeMenu.find('[data-menu-item-index]').bind(\"mouseover\", function () {\n let depth = this.getAttribute(\"data-menu-item-depth\"),\n index = this.getAttribute(\"data-menu-item-index\"),\n path = this.getAttribute(\"data-menu-item-path\"),\n $this,\n offset,\n scrollTop,\n childOpt,\n _items,\n _activeMenu;\n\n if (depth != null && typeof depth != \"undefined\") {\n _items = self.queue[depth].data[cfg.columnKeys.items][index][cfg.columnKeys.items];\n _activeMenu = self.queue[depth].$target;\n _activeMenu.find('[data-menu-item-index]').removeClass(\"hover\");\n jQuery(this).addClass(\"hover\");\n\n if (_activeMenu.attr(\"data-selected-menu-item-index\") != index) {\n _activeMenu.attr(\"data-selected-menu-item-index\", index);\n\n if (_items && _items.length > 0) {\n\n $this = jQuery(this);\n offset = $this.offset();\n scrollTop = (cfg.position == \"fixed\" ? jQuery(document).scrollTop() : 0);\n childOpt = {\n '@parent': {\n left: offset.left,\n top: offset.top,\n width: $this.outerWidth(),\n height: $this.outerHeight()\n },\n left: offset.left + $this.outerWidth() - cfg.menuBodyPadding,\n top: offset.top - cfg.menuBodyPadding - 1 - scrollTop\n };\n\n childOpt = jQuery.extend(true, opt, childOpt);\n popup.call(self, childOpt, _items, (Number(depth) + 1), path);\n }\n else {\n self.queue.splice(Number(depth) + 1).forEach(function (n) {\n n.$target.remove();\n });\n }\n }\n }\n\n depth = null;\n index = null;\n path = null;\n $this = null;\n offset = null;\n scrollTop = null;\n childOpt = null;\n _items = null;\n _activeMenu = null;\n });\n\n // mouse out\n activeMenu.find('[data-menu-item-index]').bind(\"mouseout\", function () {\n let depth = this.getAttribute(\"data-menu-item-depth\"),\n index = this.getAttribute(\"data-menu-item-index\"),\n path = this.getAttribute(\"data-menu-item-path\"),\n _items;\n\n if (path) {\n _items = self.queue[depth].data[cfg.columnKeys.items][index][cfg.columnKeys.items];\n }\n if (_items && _items.length > 0) {\n\n } else {\n jQuery(this).removeClass(\"hover\");\n }\n });\n\n // is Root\n if (depth == 0) {\n if (data.direction) activeMenu.addClass(\"direction-\" + data.direction);\n onStateChanged.call(this, null, {\n self: this,\n items: items,\n parent: (function (path) {\n if (!path) return false;\n var item = null;\n try {\n item = (Function(\"\", \"return this.config.items[\" + path.substring(5).replace(/\\./g, '].items[') + \"];\")).call(self);\n } catch (e) {\n\n }\n return item;\n })(data['@path']),\n state: \"popup\"\n });\n }\n\n align.call(this, activeMenu, data);\n onLoad.call(this, {\n self: this,\n items: items,\n element: activeMenu.get(0)\n });\n\n data = null;\n activeMenu = null;\n removed = null;\n opt = null;\n items = null;\n depth = null;\n path = null;\n\n return this;\n };\n\n const clickItem = function (opt, e) {\n let target, item;\n\n target = U.findParentNode(e.target, function (target) {\n if (target.getAttribute(\"data-menu-item-index\")) {\n return true;\n }\n });\n if (target) {\n if (typeof opt === \"undefined\") opt = {};\n item = (function (path) {\n if (!path) return false;\n let item;\n\n try {\n item = (Function(\"\", \"return this[\" + path.substring(5).replace(/\\./g, '].' + cfg.columnKeys.items + '[') + \"];\")).call(opt.items || cfg.items);\n } catch (e) {\n console.log(ax5.info.getError(\"ax5menu\", \"501\", \"menuItemClick\"));\n }\n\n try {\n return item;\n }\n finally {\n item = null;\n }\n })(target.getAttribute(\"data-menu-item-path\"));\n\n if (!item) return this;\n\n if (item.check) {\n (function (items) {\n var setValue = {\n 'checkbox': function (value) {\n this.checked = !value;\n },\n 'radio': function (value) {\n var name = this.name;\n items.forEach(function (n) {\n if (n.check && n.check.type === 'radio' && n.check.name == name) {\n n.check.checked = false;\n }\n });\n this.checked = !value;\n }\n };\n if (setValue[this.type]) setValue[this.type].call(this, this.checked);\n setValue = null;\n }).call(item.check, cfg.items);\n\n if (!cfg.itemClickAndClose) {\n self.queue.forEach(function (n) {\n n.$target.find('[data-menu-item-index]').each(function () {\n var item = n.data[cfg.columnKeys.items][this.getAttribute(\"data-menu-item-index\")];\n if (item.check) {\n jQuery(this).find(\".item-checkbox-wrap\").attr(\"data-item-checked\", item.check.checked);\n }\n });\n });\n }\n }\n\n if (self.onClick) {\n if (self.onClick.call(item, item, opt.param)) {\n self.close();\n }\n }\n if ((!item[cfg.columnKeys.items] || item[cfg.columnKeys.items].length == 0) && cfg.itemClickAndClose) self.close();\n }\n else {\n self.close();\n }\n\n target = null;\n item = null;\n return this;\n };\n\n const align = function (activeMenu, data) {\n let $window = jQuery(window),\n $document = jQuery(document),\n wh = (cfg.position == \"fixed\") ? $window.height() : $document.height(),\n ww = $window.width(),\n h = activeMenu.outerHeight(),\n w = activeMenu.outerWidth(),\n l = data.left,\n t = data.top,\n position = cfg.position || \"fixed\";\n\n if (l + w > ww) {\n if (data['@parent']) {\n l = data['@parent'].left - w + cfg.menuBodyPadding;\n }\n else {\n l = ww - w;\n }\n }\n\n if (t + h > wh) {\n t = wh - h;\n }\n\n activeMenu.css({left: l, top: t, position: position});\n\n activeMenu = null;\n data = null;\n $window = null;\n $document = null;\n wh = null;\n ww = null;\n h = null;\n w = null;\n l = null;\n t = null;\n position = null;\n return this;\n };\n\n /// private end\n\n this.init = function () {\n /**\n * config에 선언된 이벤트 함수들을 this로 이동시켜 주어 나중에 인스턴스.on... 으로 처리 가능 하도록 변경\n */\n this.onStateChanged = cfg.onStateChanged;\n this.onClick = cfg.onClick;\n this.onLoad = cfg.onLoad;\n\n onStateChanged.call(this, null, {\n self: this,\n state: \"init\"\n });\n };\n\n /**\n * @method ax5.ui.menu.popup\n * @param {Event|Object} e - Event or Object\n * @param {Object} [opt]\n * @param {String} [opt.theme]\n * @param {Function} [opt.filter]\n * @returns {ax5.ui.menu} this\n */\n this.popup = (function () {\n\n let getOption = {\n 'event': function (e, opt) {\n //var xOffset = Math.max(document.documentElement.scrollLeft, document.body.scrollLeft);\n //var yOffset = Math.max(document.documentElement.scrollTop, document.body.scrollTop);\n //console.log(e.pageY);\n\n e = {\n left: e.clientX,\n top: (cfg.position == \"fixed\") ? e.clientY : e.pageY,\n width: cfg.width,\n theme: cfg.theme\n };\n\n e.left -= 5;\n e.top -= 5;\n\n if (cfg.offset) {\n if (cfg.offset.left) e.left += cfg.offset.left;\n if (cfg.offset.top) e.top += cfg.offset.top;\n }\n opt = jQuery.extend(true, e, opt);\n\n try {\n return opt;\n }\n finally {\n e = null;\n //opt = null;\n }\n },\n 'object': function (e, opt) {\n e = {\n left: e.left,\n top: e.top,\n width: e.width || cfg.width,\n theme: e.theme || cfg.theme\n };\n\n if (cfg.offset) {\n if (cfg.offset.left) e.left += cfg.offset.left;\n if (cfg.offset.top) e.top += cfg.offset.top;\n }\n\n opt = jQuery.extend(true, e, opt);\n\n try {\n return opt;\n }\n finally {\n e = null;\n //opt = null;\n }\n }\n },\n updateTheme = function (theme) {\n if (theme) cfg.theme = theme;\n };\n\n return function (e, opt) {\n\n if (!e) return this;\n opt = getOption[((typeof e.clientX == \"undefined\") ? \"object\" : \"event\")].call(this, e, opt);\n updateTheme(opt.theme);\n\n let items = [].concat(cfg.items),\n filteringItem;\n opt.items = items;\n\n if (opt.filter) {\n filteringItem = function (_items) {\n let arr = [];\n _items.forEach(function (n) {\n if (n.items && n.items.length > 0) {\n n.items = filteringItem(n.items);\n }\n if (opt.filter.call(n)) {\n arr.push(n);\n }\n });\n return arr;\n };\n opt.items = items = filteringItem(items);\n }\n\n if (items.length) {\n appEventAttach.call(this, false);\n popup.call(this, opt, items, 0); // 0 is seq of queue\n\n if (this.popupEventAttachTimer) clearTimeout(this.popupEventAttachTimer);\n this.popupEventAttachTimer = setTimeout((function () {\n appEventAttach.call(this, true, opt); // 이벤트 연결\n }).bind(this), 500);\n }\n\n e = null;\n return this;\n }\n })();\n\n /**\n * @method ax5.ui.menu.attach\n * @param {Element|jQueryObject} el\n * @returns {ax5.ui.menu} this\n */\n this.attach = (function () {\n\n var getOption = {\n 'object': function (e, opt) {\n e = {\n left: e.left,\n top: e.top,\n width: e.width || cfg.width,\n theme: e.theme || cfg.theme,\n direction: e.direction || cfg.direction\n };\n opt = jQuery.extend(true, opt, e);\n\n try {\n return opt;\n }\n finally {\n e = null;\n opt = null;\n }\n }\n };\n\n var popUpChildMenu = function (target, opt, eType) {\n var\n $target = jQuery(target),\n offset = $target.offset(),\n height = $target.outerHeight(),\n index = Number(target.getAttribute(\"data-menu-item-index\")),\n scrollTop = (cfg.position == \"fixed\") ? jQuery(document).scrollTop() : 0;\n\n if (cfg.items && cfg.items[index][cfg.columnKeys.items] && cfg.items[index][cfg.columnKeys.items].length) {\n\n if (self.menuBar.openedIndex == index) {\n if (eType == \"click\") self.close();\n return false;\n }\n\n self.menuBar.target.find('[data-menu-item-index]').removeClass(\"hover\");\n self.menuBar.opened = true;\n self.menuBar.openedIndex = index;\n\n $target.attr(\"data-menu-item-opened\", \"true\");\n $target.addClass(\"hover\");\n\n if (cfg.offset) {\n if (cfg.offset.left) offset.left += cfg.offset.left;\n if (cfg.offset.top) offset.top += cfg.offset.top;\n }\n\n opt = getOption[\"object\"].call(this, {left: offset.left, top: offset.top + height - scrollTop}, opt);\n\n popup.call(self, opt, cfg.items[index][cfg.columnKeys.items], 0, 'root.' + target.getAttribute(\"data-menu-item-index\")); // 0 is seq of queue\n appEventAttach.call(self, true, {}); // 이벤트 연결\n }\n\n target = null;\n opt = null;\n $target = null;\n offset = null;\n height = null;\n index = null;\n scrollTop = null;\n };\n var clickParentMenu = function (target, opt, eType) {\n var\n $target = jQuery(target),\n offset = $target.offset(),\n height = $target.outerHeight(),\n index = Number(target.getAttribute(\"data-menu-item-index\")),\n scrollTop = (cfg.position == \"fixed\") ? jQuery(document).scrollTop() : 0;\n if (cfg.items && (!cfg.items[index][cfg.columnKeys.items] || cfg.items[index][cfg.columnKeys.items].length == 0)) {\n if (self.onClick) {\n self.onClick.call(cfg.items[index], cfg.items[index]);\n }\n }\n };\n\n return function (el, opt) {\n var\n data = {},\n items = cfg.items,\n activeMenu;\n\n if (typeof opt === \"undefined\") opt = {};\n\n data.theme = opt.theme || cfg.theme;\n data.cfg = {\n icons: jQuery.extend({}, cfg.icons),\n iconWidth: opt.iconWidth || cfg.iconWidth,\n acceleratorWidth: opt.acceleratorWidth || cfg.acceleratorWidth\n };\n\n items.forEach(function (n) {\n if (n.html || n.divide) {\n n['@isMenu'] = false;\n if (n.html) {\n n['@html'] = n.html.call({\n item: n,\n config: cfg,\n opt: opt\n });\n }\n }\n else {\n n['@isMenu'] = true;\n }\n });\n\n data[cfg.columnKeys.items] = items;\n\n activeMenu = jQuery(MENU.tmpl.get.call(this, \"tmplMenubar\", data, cfg.columnKeys));\n self.menuBar = {\n target: jQuery(el),\n opened: false\n };\n self.menuBar.target.html(activeMenu);\n\n // click, mouseover\n self.menuBar.target.bind(\"click\", function (e) {\n if (!e) return this;\n var target = U.findParentNode(e.target, function (target) {\n if (target.getAttribute(\"data-menu-item-index\")) {\n return true;\n }\n });\n if (target) {\n clickParentMenu(target, opt, \"click\");\n popUpChildMenu(target, opt, \"click\");\n }\n\n target = null;\n });\n self.menuBar.target.bind(\"mouseover\", function (e) {\n if (!self.menuBar.opened) return false;\n var target = U.findParentNode(e.target, function (target) {\n if (target.getAttribute(\"data-menu-item-index\")) {\n return true;\n }\n });\n if (target) popUpChildMenu(target, opt, \"mouseover\");\n\n target = null;\n });\n\n el = null;\n opt = null;\n data = null;\n items = null;\n activeMenu = null;\n\n return this;\n }\n })();\n\n /**\n * @method ax5.ui.menu.close\n * @returns {ax5.ui.menu} this\n */\n this.close = function () {\n\n if (self.menuBar && self.menuBar.target) {\n self.menuBar.target.find('[data-menu-item-index]').removeClass(\"hover\");\n self.menuBar.opened = false;\n self.menuBar.openedIndex = null;\n }\n\n appEventAttach.call(this, false); // 이벤트 제거\n\n this.queue.forEach(function (n) {\n n.$target.remove();\n });\n this.queue = [];\n\n onStateChanged.call(this, null, {\n self: this,\n state: \"close\"\n });\n\n return this;\n };\n\n /**\n * @method ax5.ui.menu.getCheckValue\n * @returns {Object} statusCheckItem\n */\n this.getCheckValue = function () {\n var checkItems = {},\n collectItem = function (items) {\n var i = items.length;\n while (i--) {\n if (items[i].check && items[i].check.checked) {\n if (!checkItems[items[i].check.name]) checkItems[items[i].check.name] = items[i].check.value;\n else {\n if (U.isString(checkItems[items[i].check.name])) checkItems[items[i].check.name] = [checkItems[items[i].check.name]];\n checkItems[items[i].check.name].push(items[i].check.value);\n }\n }\n if (items[i].items && items[i].items.length > 0) collectItem(items[i].items);\n }\n };\n\n collectItem(cfg.items);\n\n try {\n return checkItems;\n }\n finally {\n checkItems = null;\n collectItem = null;\n }\n };\n\n // 클래스 생성자\n this.main = (function () {\n\n UI.menu_instance = UI.menu_instance || [];\n UI.menu_instance.push(this);\n\n if (arguments && U.isObject(arguments[0])) {\n this.setConfig(arguments[0]);\n }\n }).apply(this, arguments);\n };\n })());\n\n MENU = ax5.ui.menu;\n})();\n\n// todo : menu 드랍다운 아이콘 설정 기능 추가","// ax5.ui.menu.tmpl\n(function () {\n var MENU = ax5.ui.menu;\n \n var tmpl = function (columnKeys) {\n return `\n
\n
\n {{#${columnKeys.items}}}\n {{^@isMenu}}\n {{#divide}}\n
\n {{/divide}}\n {{#html}}\n
{{{@html}}}
\n {{/html}}\n {{/@isMenu}}\n {{#@isMenu}}\n
\n \n {{#check}}\n \n {{/check}}\n {{^check}}\n \n {{/check}}\n \n {{#icon}}\n {{{.}}}\n {{/icon}}\n {{{${columnKeys.label}}}}\n {{#accelerator}}\n {{.}}\n {{/accelerator}}\n {{#@hasChild}}\n {{{cfg.icons.arrow}}}\n {{/@hasChild}}\n
\n {{/@isMenu}}\n\n {{/${columnKeys.items}}}\n
\n
\n
\n `;\n };\n var tmplMenubar = function (columnKeys) {\n return `\n
\n
\n {{#${columnKeys.items}}}\n {{^@isMenu}}\n {{#divide}}\n
\n {{/divide}}\n {{#html}}\n
{{{@html}}}
\n {{/html}}\n {{/@isMenu}}\n {{#@isMenu}}\n
\n {{#icon}}\n {{{.}}}\n {{/icon}}\n {{{${columnKeys.label}}}}\n
\n {{/@isMenu}}\n {{/${columnKeys.items}}}\n
\n
\n `;\n };\n\n MENU.tmpl = {\n \"tmpl\" : tmpl,\n \"tmplMenubar\" : tmplMenubar,\n\n get: function (tmplName, data, columnKeys) {\n return ax5.mustache.render(MENU.tmpl[tmplName].call(this, columnKeys), data);\n }\n };\n})();"]} --------------------------------------------------------------------------------