├── .gitattributes ├── .gitignore ├── LICENSE ├── bower.json ├── demos ├── addons │ ├── fadeIn.html │ └── popIn.html ├── closeFunction.html ├── customPosition.html ├── default.html ├── jQuery.html ├── position.html ├── scroll.html └── themes │ ├── bright.html │ ├── dark.html │ └── default.html ├── dist ├── addons │ ├── fadein.min.css │ └── popin.min.css ├── basicContext.min.css ├── basicContext.min.js └── themes │ ├── bright.min.css │ ├── dark.min.css │ ├── default.min.css │ └── light.min.css ├── gulpfile.js ├── package.json ├── readme.md └── src ├── scripts └── basicContext.js └── styles ├── _container.scss ├── _context.scss ├── _vars.scss ├── addons ├── fadein.scss └── popin.scss ├── main.scss └── themes ├── bright.scss ├── dark.scss └── default.scss /.gitattributes: -------------------------------------------------------------------------------- 1 | dist/* binary -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | etc/ 2 | 3 | node_modules/ 4 | bower_components/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Tobias Reich 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. 22 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basicContext", 3 | "authors": [ 4 | "Tobias Reich " 5 | ], 6 | "description": "Easy-to-use context-menu for your website or webapp", 7 | "main": "dist/basicContext.min.js", 8 | "keywords": [ 9 | "context", 10 | "menu", 11 | "popup", 12 | "right-click" 13 | ], 14 | "license": "MIT", 15 | "homepage": "http://github.com/electerious/basicContext", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/electerious/basicContext.git" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /demos/addons/fadeIn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | basicContext Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 68 | 69 | Click me! 70 | 71 | Click me! 72 | 73 | Click me! 74 | 75 | Click me! 76 | 77 | Click me! 78 | 79 | 80 | 81 | 113 | 114 | -------------------------------------------------------------------------------- /demos/addons/popIn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | basicContext Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 68 | 69 | Click me! 70 | 71 | Click me! 72 | 73 | Click me! 74 | 75 | Click me! 76 | 77 | Click me! 78 | 79 | 80 | 81 | 113 | 114 | -------------------------------------------------------------------------------- /demos/closeFunction.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | basicContext Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 44 | 45 | Click me! 46 | 47 | 48 | 49 | 86 | 87 | -------------------------------------------------------------------------------- /demos/customPosition.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | basicContext Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 40 | 41 | Click me! 42 | 43 | 44 | 45 | 77 | 78 | -------------------------------------------------------------------------------- /demos/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | basicContext Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 40 | 41 | Click me! 42 | 43 | Right-Click me! 44 | 45 | Touch me! (mobile only) 46 | 47 | 48 | 49 | 78 | 79 | -------------------------------------------------------------------------------- /demos/jQuery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | basicContext Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 40 | 41 | Click me! 42 | 43 | Right-Click me! 44 | 45 | Touch me! (mobile only) 46 | 47 | 48 | 49 | 50 | 79 | 80 | -------------------------------------------------------------------------------- /demos/position.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | basicContext Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 67 | 68 | Click me! 69 | 70 | Click me! 71 | 72 | Click me! 73 | 74 | Click me! 75 | 76 | Click me! 77 | 78 | 79 | 80 | 112 | 113 | -------------------------------------------------------------------------------- /demos/scroll.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | basicContext Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 40 | 41 | Small menu 42 | 43 | Big menu 44 | 45 | 46 | 47 | 142 | 143 | -------------------------------------------------------------------------------- /demos/themes/bright.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | basicContext Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 40 | 41 | Small menu 42 | 43 | Big menu 44 | 45 | 46 | 47 | 142 | 143 | -------------------------------------------------------------------------------- /demos/themes/dark.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | basicContext Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 40 | 41 | Small menu 42 | 43 | Big menu 44 | 45 | 46 | 47 | 142 | 143 | -------------------------------------------------------------------------------- /demos/themes/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | basicContext Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 40 | 41 | Small menu 42 | 43 | Big menu 44 | 45 | 46 | 47 | 142 | 143 | -------------------------------------------------------------------------------- /dist/addons/fadein.min.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes basicContext__fadeIn{0%{opacity:0}100%{opacity:1}}@keyframes basicContext__fadeIn{0%{opacity:0}100%{opacity:1}}.basicContext{-webkit-animation:basicContext__fadeIn .1s ease;animation:basicContext__fadeIn .1s ease} -------------------------------------------------------------------------------- /dist/addons/popin.min.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes basicContext__popIn{0%{-webkit-transform:scale(0);transform:scale(0)}100%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes basicContext__popIn{0%{-webkit-transform:scale(0);transform:scale(0)}100%{-webkit-transform:scale(1);transform:scale(1)}}.basicContext{-webkit-animation:basicContext__popIn .3s cubic-bezier(.51,.92,.24,1.2);animation:basicContext__popIn .3s cubic-bezier(.51,.92,.24,1.2)} -------------------------------------------------------------------------------- /dist/basicContext.min.css: -------------------------------------------------------------------------------- 1 | .basicContext,.basicContext *{box-sizing:border-box}.basicContextContainer{position:fixed;width:100%;height:100%;top:0;left:0;z-index:1000;-webkit-tap-highlight-color:transparent}.basicContext{position:absolute;opacity:0;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.basicContext__item{cursor:pointer}.basicContext__item--separator{float:left;width:100%;height:1px;cursor:default}.basicContext__item--disabled{cursor:default}.basicContext__data{min-width:140px;padding-right:20px;text-align:left;white-space:nowrap}.basicContext__icon{display:inline-block}.basicContext--scrollable{height:100%;-webkit-overflow-scrolling:touch;overflow-y:auto}.basicContext--scrollable .basicContext__data{min-width:160px} -------------------------------------------------------------------------------- /dist/basicContext.min.js: -------------------------------------------------------------------------------- 1 | "use strict";!function(n,t){"undefined"!=typeof module&&module.exports?module.exports=t():"function"==typeof define&&define.amd?define(t):window[n]=t()}("basicContext",function(){var n=null,t="item",e="separator",i=function(){var n=arguments.length<=0||void 0===arguments[0]?"":arguments[0];return document.querySelector(".basicContext "+n)},l=function(){var n=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],i=0===Object.keys(n).length?!0:!1;return i===!0&&(n.type=e),null==n.type&&(n.type=t),null==n["class"]&&(n["class"]=""),n.visible!==!1&&(n.visible=!0),null==n.icon&&(n.icon=null),null==n.title&&(n.title="Undefined"),n.disabled!==!0&&(n.disabled=!1),n.disabled===!0&&(n["class"]+=" basicContext__item--disabled"),null==n.fn&&n.type!==e&&n.disabled===!1?(console.warn("Missing fn for item '"+n.title+"'"),!1):!0},o=function(n,i){var o="",r="";return l(n)===!1?"":n.visible===!1?"":(n.num=i,null!==n.icon&&(r=""),n.type===t?o="\n \n "+r+n.title+"\n \n ":n.type===e&&(o="\n \n "),o)},r=function(n){var t="";return t+="\n
\n
\n \n \n ",n.forEach(function(n,e){return t+=o(n,e)}),t+="\n \n
\n
\n
\n "},a=function(){var n=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],t={x:n.clientX,y:n.clientY};if("touchend"===n.type&&(null==t.x||null==t.y)){var e=n.changedTouches;null!=e&&e.length>0&&(t.x=e[0].clientX,t.y=e[0].clientY)}return(null==t.x||t.x<0)&&(t.x=0),(null==t.y||t.y<0)&&(t.y=0),t},s=function(n,t){var e=a(n),i=e.x,l=e.y,o={width:window.innerWidth,height:window.innerHeight},r={width:t.offsetWidth,height:t.offsetHeight};i+r.width>o.width&&(i-=i+r.width-o.width),l+r.height>o.height&&(l-=l+r.height-o.height),r.height>o.height&&(l=0,t.classList.add("basicContext--scrollable"));var s=e.x-i,u=e.y-l;return{x:i,y:l,rx:s,ry:u}},u=function(){var n=arguments.length<=0||void 0===arguments[0]?{}:arguments[0];return null==n.fn?!1:n.visible===!1?!1:n.disabled===!0?!1:(i("td[data-num='"+n.num+"']").onclick=n.fn,i("td[data-num='"+n.num+"']").oncontextmenu=n.fn,!0)},c=function(t,e,l,o){var a=r(t);document.body.insertAdjacentHTML("beforeend",a),null==n&&(n=document.body.style.overflow,document.body.style.overflow="hidden");var c=i(),d=s(e,c);return c.style.left=d.x+"px",c.style.top=d.y+"px",c.style.transformOrigin=d.rx+"px "+d.ry+"px",c.style.opacity=1,null==l&&(l=f),c.parentElement.onclick=l,c.parentElement.oncontextmenu=l,t.forEach(u),"function"==typeof e.preventDefault&&e.preventDefault(),"function"==typeof e.stopPropagation&&e.stopPropagation(),"function"==typeof o&&o(),!0},d=function(){var n=i();return null==n||0===n.length?!1:!0},f=function(){if(d()===!1)return!1;var t=document.querySelector(".basicContextContainer");return t.parentElement.removeChild(t),null!=n&&(document.body.style.overflow=n,n=null),!0};return{ITEM:t,SEPARATOR:e,show:c,visible:d,close:f}}); -------------------------------------------------------------------------------- /dist/themes/bright.min.css: -------------------------------------------------------------------------------- 1 | .basicContext{padding:6px 0;background-color:#fff;box-shadow:0 2px 15px rgba(0,0,0,.2),0 0 0 1px #eee;border-radius:3px}.basicContext__item{margin-bottom:2px}.basicContext__item--separator{margin:4px 0;background-color:#eee}.basicContext__item--disabled{opacity:.5}.basicContext__item:last-child{margin-bottom:0}.basicContext__data{padding:6px 14px;color:#666}.basicContext__item:not(.basicContext__item--disabled):hover .basicContext__data{color:#4393e6;background-color:rgba(67,147,230,.1)}.basicContext__item:not(.basicContext__item--disabled):active .basicContext__data{color:#1d79d9;background-color:rgba(67,147,230,.15)}.basicContext__icon{margin-right:8px;width:12px;text-align:center} -------------------------------------------------------------------------------- /dist/themes/dark.min.css: -------------------------------------------------------------------------------- 1 | .basicContext{padding:6px 0;background-color:rgba(40,40,40,.98);box-shadow:0 2px 15px rgba(0,0,0,.4),0 0 0 1px #222;border-radius:3px}.basicContext__item{margin-bottom:2px}.basicContext__item--separator{margin:4px 0;background-color:#222}.basicContext__item--disabled{opacity:.5}.basicContext__item:last-child{margin-bottom:0}.basicContext__data{padding:6px 14px;color:#fff}.basicContext__item:not(.basicContext__item--disabled):hover .basicContext__data{background-color:#4393e6}.basicContext__item:not(.basicContext__item--disabled):active .basicContext__data{background-color:#1d79d9}.basicContext__icon{margin-right:8px;width:12px;text-align:center} -------------------------------------------------------------------------------- /dist/themes/default.min.css: -------------------------------------------------------------------------------- 1 | .basicContext{padding:6px;background-color:#fff;box-shadow:0 1px 2px rgba(0,0,0,.4),0 0 1px rgba(0,0,0,.2);border-radius:3px}.basicContext__item{margin-bottom:2px}.basicContext__item--separator{margin:4px 0;background-color:rgba(0,0,0,.1)}.basicContext__item--disabled{opacity:.5}.basicContext__item:last-child{margin-bottom:0}.basicContext__data{padding:6px 8px;color:#333;border-radius:2px}.basicContext__item:not(.basicContext__item--disabled):hover .basicContext__data{color:#fff;background-color:#4393e6}.basicContext__item:not(.basicContext__item--disabled):active .basicContext__data{background-color:#1d79d9}.basicContext__icon{margin-right:10px;width:12px;text-align:center} -------------------------------------------------------------------------------- /dist/themes/light.min.css: -------------------------------------------------------------------------------- 1 | .basicContext{padding:6px 0;background-color:#fff;box-shadow:0 2px 15px rgba(0,0,0,.2),0 0 0 1px #eee;border-radius:3px}.basicContext__item{margin-bottom:2px}.basicContext__item--separator{margin:4px 0;background-color:#eee}.basicContext__item--disabled{opacity:.5}.basicContext__item:last-child{margin-bottom:0}.basicContext__data{padding:6px 14px;color:#666}.basicContext__item:not(.basicContext__item--disabled):hover .basicContext__data{color:#4393e6;background-color:rgba(67,147,230,.1)}.basicContext__item:not(.basicContext__item--disabled):active .basicContext__data{color:#1d79d9;background-color:rgba(67,147,230,.15)}.basicContext__icon{margin-right:8px;width:12px;text-align:center} -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var name = require('./package.json').moduleName, 2 | fs = require('fs'), 3 | gulp = require('gulp'), 4 | plugins = require('gulp-load-plugins')() 5 | 6 | var head = fs.readFileSync('./node_modules/@electerious/modulizer/head.js', { encoding: 'utf8' }), 7 | foot = fs.readFileSync('./node_modules/@electerious/modulizer/foot.js', { encoding: 'utf8' }) 8 | 9 | var catchError = function(err) { 10 | 11 | console.log(err.toString()) 12 | this.emit('end') 13 | 14 | } 15 | 16 | gulp.task('styles', function() { 17 | 18 | gulp.src('./src/styles/main.scss') 19 | .pipe(plugins.sass()) 20 | .on('error', catchError) 21 | .pipe(plugins.concat(name + '.min.css', { newLine: "\n" })) 22 | .pipe(plugins.autoprefixer('last 2 version', '> 1%')) 23 | .pipe(plugins.minifyCss()) 24 | .pipe(gulp.dest('./dist')) 25 | 26 | gulp.src('./src/styles/themes/*.scss') 27 | .pipe(plugins.sass()) 28 | .on('error', catchError) 29 | .pipe(plugins.rename(function(path) { path.basename += '.min' })) 30 | .pipe(plugins.autoprefixer('last 2 version', '> 1%')) 31 | .pipe(plugins.minifyCss()) 32 | .pipe(gulp.dest('./dist/themes')) 33 | 34 | gulp.src('./src/styles/addons/*.scss') 35 | .pipe(plugins.sass()) 36 | .on('error', catchError) 37 | .pipe(plugins.rename(function(path) { path.basename += '.min' })) 38 | .pipe(plugins.autoprefixer('last 2 version', '> 1%')) 39 | .pipe(plugins.minifyCss()) 40 | .pipe(gulp.dest('./dist/addons')) 41 | 42 | }) 43 | 44 | gulp.task('scripts', function() { 45 | 46 | gulp.src('./src/scripts/*.js') 47 | .pipe(plugins.header(head, { name: name })) 48 | .pipe(plugins.footer(foot)) 49 | .pipe(plugins.babel()) 50 | .on('error', catchError) 51 | .pipe(plugins.concat(name + '.min.js', { newLine: "\n" })) 52 | .pipe(plugins.uglify()) 53 | .on('error', catchError) 54 | .pipe(gulp.dest('./dist')) 55 | 56 | }) 57 | 58 | gulp.task('default', ['styles', 'scripts']) 59 | 60 | gulp.task('watch', ['styles', 'scripts'], function() { 61 | 62 | gulp.watch('./src/styles/**/*.scss', ['styles']) 63 | gulp.watch('./src/scripts/**/*.js', ['scripts']) 64 | 65 | }) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basiccontext", 3 | "moduleName": "basicContext", 4 | "version": "3.5.1", 5 | "authors": [ 6 | "Tobias Reich " 7 | ], 8 | "description": "Easy-to-use context-menu for your website or webapp", 9 | "main": "dist/basicContext.min.js", 10 | "keywords": [ 11 | "context", 12 | "menu", 13 | "popup", 14 | "right-click" 15 | ], 16 | "license": "MIT", 17 | "homepage": "http://github.com/electerious/basicContext", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/electerious/basicContext.git" 21 | }, 22 | "devDependencies": { 23 | "@electerious/modulizer": "^1.0.1", 24 | "gulp": "^3.9.0", 25 | "gulp-autoprefixer": "3.1.0", 26 | "gulp-babel": "^5.2.1", 27 | "gulp-concat": "^2.6.0", 28 | "gulp-footer": "^1.0.5", 29 | "gulp-header": "^1.7.1", 30 | "gulp-load-plugins": "^1.2.0", 31 | "gulp-minify-css": "^1.2.3", 32 | "gulp-rename": "^1.2.2", 33 | "gulp-sass": "^2.1.1", 34 | "gulp-uglify": "^1.5.1", 35 | "gulp-util": "^3.0.7" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # [Deprecated] basicContext 2 | 3 | Easy-to-use context-menu for your website or web-app. 4 | 5 | 6 | 7 | ## Contents 8 | 9 | - [Demos](#demos) 10 | - [Features](#features) 11 | - [Requirements](#requirements) 12 | - [Setup](#setup) 13 | - [How to use](#how-to-use) 14 | - [Vanilla JS](#vanilla-js) 15 | - [jQuery](#jquery) 16 | - [Themes](#themes) 17 | - [Addons](#addons) 18 | 19 | ## Demos 20 | 21 | | Name | Description | Link | 22 | |:-----------|:------------|:------------| 23 | | Basic demo | basicContext works with all kind of events | [Try it on CodePen](http://codepen.io/electerious/pen/emaJxE) | 24 | | Position demo | basicContext never leaves the visible screen-area | [Try it on CodePen](http://codepen.io/electerious/pen/GJqrZN) | 25 | | Scroll demo | basicContext is scrollable when the context height is bigger than the browser height | [Try it on CodePen](http://codepen.io/electerious/pen/aOZpZr) | 26 | | Custom close function demo | basicContext lets you define a custom close function | [Try it on CodePen](http://codepen.io/electerious/pen/MwpVdE) | 27 | | Custom position demo | basicContext accepts an object with custom coordinates | [Try it on CodePen](http://codepen.io/electerious/pen/PqjMrN) | 28 | | jQuery demo | Use basicContext with the jQuery [Event Object](http://api.jquery.com/category/events/event-object/) | [Demo](demos/jQuery.html) | 29 | 30 | ## Features 31 | 32 | - Works in all modern browsers 33 | - Written in Vanilla JS 34 | - CommonJS and AMD support 35 | - Layout and theme are separated CSS-files. This makes it easy to style our own context. 36 | - Stays within the viewport and never opens outside the visible screen-area ([Demo](http://codepen.io/electerious/pen/GJqrZN)) 37 | - Scrollable, when the height of the context-menu is bigger than the height of the browser ([Demo](http://codepen.io/electerious/pen/aOZpZr)) 38 | 39 | ## Requirements 40 | 41 | basicContext is written in Vanilla JS and only dependents on the following browser APIs: 42 | 43 | - [classList](http://caniuse.com/#feat=classlist) 44 | 45 | All of these APIs are capable of being polyfilled in older browser. Check the linked resources above to determine if you must polyfill to achieve your desired level of browser support. 46 | 47 | ## Setup 48 | 49 | We recommend to install basicContext using [Bower](http://bower.io/) or [npm](https://npmjs.com). 50 | 51 | ```sh 52 | bower install basicContext 53 | ``` 54 | ```sh 55 | npm install basiccontext 56 | ``` 57 | 58 | Include the CSS-files in the `head` and the JS-file at the end of your `body`: 59 | 60 | ```html 61 | 62 | 63 | ``` 64 | ```html 65 | 66 | ``` 67 | 68 | Skip the JS-file if you want to use basicContext as module together with [Browserify](http://browserify.org): 69 | 70 | ```js 71 | let basicContext = require('basiccontext') 72 | ``` 73 | 74 | ## How to use 75 | 76 | ### Vanilla JS 77 | 78 | Show a context-menu by using the following command: 79 | 80 | ```js 81 | document.querySelector('.btn').addEventListener('click', function(e) { 82 | 83 | let items = [ 84 | { title: 'Add Sites', icon: 'ion-plus-round', fn: clicked }, 85 | { title: 'Reset Login', icon: 'ion-person', fn: clicked }, 86 | { title: 'Help', icon: 'ion-help-buoy', fn: clicked }, 87 | { title: 'Disabled', icon: 'ion-minus-circled', fn: clicked, disabled: true }, 88 | { title: 'Invisible', icon: 'ion-eye-disabled', fn: clicked, visible: false }, 89 | { }, 90 | { title: 'Logout', icon: 'ion-log-out', fn: clicked } 91 | ] 92 | 93 | basicContext.show(items, e) 94 | 95 | }) 96 | ``` 97 | 98 | ### jQuery 99 | 100 | basicContext doesn't work properly with the normalized jQuery [Event Object](http://api.jquery.com/category/events/event-object/), but you can easily bypass this issue using `e.originalEvent`: 101 | 102 | ```js 103 | $('.btn').on('click', function(e) { 104 | 105 | let items = [ 106 | { title: 'Add Sites', icon: 'ion-plus-round', fn: clicked }, 107 | { title: 'Reset Login', icon: 'ion-person', fn: clicked }, 108 | { title: 'Help', icon: 'ion-help-buoy', fn: clicked }, 109 | { title: 'Disabled', icon: 'ion-minus-circled', fn: clicked, disabled: true }, 110 | { title: 'Invisible', icon: 'ion-eye-disabled', fn: clicked, visible: false }, 111 | { }, 112 | { title: 'Logout', icon: 'ion-log-out', fn: clicked } 113 | ] 114 | 115 | basicContext.show(items, e.originalEvent) 116 | 117 | }) 118 | ``` 119 | 120 | ## Themes 121 | 122 | Layout and theme are separated CSS-files. This makes it easy to style your own context or to choose from the included themes. 123 | 124 | | Name | Preview | CSS-File | Demo | 125 | |:-----------|:------------|:------------|:------------| 126 | | Default theme | | [CSS-File](dist/themes/default.min.css) | [Demo](demos/themes/default.html) | 127 | | Bright theme | | [CSS-File](dist/themes/bright.min.css) | [Demo](demos/themes/bright.html) | 128 | | Dark theme | | [CSS-File](dist/themes/dark.min.css) | [Demo](demos/themes/dark.html) | 129 | 130 | ## Addons 131 | 132 | Include the following CSS-files to enhance the look and functionality of your contexts. 133 | 134 | | Name | Preview | CSS-File | Demo | 135 | |:-----------|:------------|:------------|:------------| 136 | | PopIn effect | | [CSS-File](dist/addons/popin.min.css) | [Demo](demos/addons/popIn.html) | 137 | | FadeIn effect | | [CSS-File](dist/addons/fadein.min.css) | [Demo](demos/addons/fadeIn.html) | 138 | -------------------------------------------------------------------------------- /src/scripts/basicContext.js: -------------------------------------------------------------------------------- 1 | let overflow = null 2 | 3 | const ITEM = 'item', 4 | SEPARATOR = 'separator' 5 | 6 | const dom = function(elem = '') { 7 | 8 | return document.querySelector('.basicContext ' + elem) 9 | 10 | } 11 | 12 | const valid = function(item = {}) { 13 | 14 | let emptyItem = (Object.keys(item).length===0 ? true : false) 15 | 16 | if (emptyItem===true) item.type = SEPARATOR 17 | if (item.type==null) item.type = ITEM 18 | if (item.class==null) item.class = '' 19 | if (item.visible!==false) item.visible = true 20 | if (item.icon==null) item.icon = null 21 | if (item.title==null) item.title = 'Undefined' 22 | 23 | // Add disabled class when item disabled 24 | if (item.disabled!==true) item.disabled = false 25 | if (item.disabled===true) item.class += ' basicContext__item--disabled' 26 | 27 | // Item requires a function when 28 | // it's not a separator and not disabled 29 | if (item.fn==null && item.type!==SEPARATOR && item.disabled===false) { 30 | 31 | console.warn(`Missing fn for item '${ item.title }'`) 32 | return false 33 | 34 | } 35 | 36 | return true 37 | 38 | } 39 | 40 | const buildItem = function(item, num) { 41 | 42 | let html = '', 43 | span = '' 44 | 45 | // Parse and validate item 46 | if (valid(item)===false) return '' 47 | 48 | // Skip when invisible 49 | if (item.visible===false) return '' 50 | 51 | // Give item a unique number 52 | item.num = num 53 | 54 | // Generate span/icon-element 55 | if (item.icon!==null) span = `` 56 | 57 | // Generate item 58 | if (item.type===ITEM) { 59 | 60 | html = ` 61 | 62 | ${ span }${ item.title } 63 | 64 | ` 65 | 66 | } else if (item.type===SEPARATOR) { 67 | 68 | html = ` 69 | 70 | ` 71 | 72 | } 73 | 74 | return html 75 | 76 | } 77 | 78 | const build = function(items) { 79 | 80 | let html = '' 81 | 82 | html += ` 83 |
84 |
85 | 86 | 87 | ` 88 | 89 | items.forEach((item, i) => html += buildItem(item, i)) 90 | 91 | html += ` 92 | 93 |
94 |
95 |
96 | ` 97 | 98 | return html 99 | 100 | } 101 | 102 | const getNormalizedEvent = function(e = {}) { 103 | 104 | let pos = { 105 | x : e.clientX, 106 | y : e.clientY 107 | } 108 | 109 | if (e.type==='touchend' && (pos.x==null || pos.y==null)) { 110 | 111 | // We need to capture clientX and clientY from original event 112 | // when the event 'touchend' does not return the touch position 113 | 114 | let touches = e.changedTouches 115 | 116 | if (touches!=null&&touches.length>0) { 117 | pos.x = touches[0].clientX 118 | pos.y = touches[0].clientY 119 | } 120 | 121 | } 122 | 123 | // Position unknown 124 | if (pos.x==null || pos.x < 0) pos.x = 0 125 | if (pos.y==null || pos.y < 0) pos.y = 0 126 | 127 | return pos 128 | 129 | } 130 | 131 | const getPosition = function(e, context) { 132 | 133 | // Get the click position 134 | let normalizedEvent = getNormalizedEvent(e) 135 | 136 | // Set the initial position 137 | let x = normalizedEvent.x, 138 | y = normalizedEvent.y 139 | 140 | // Get size of browser 141 | let browserSize = { 142 | width : window.innerWidth, 143 | height : window.innerHeight 144 | } 145 | 146 | // Get size of context 147 | let contextSize = { 148 | width : context.offsetWidth, 149 | height : context.offsetHeight 150 | } 151 | 152 | // Fix position based on context and browser size 153 | if ((x + contextSize.width) > browserSize.width) x = x - ((x + contextSize.width) - browserSize.width) 154 | if ((y + contextSize.height) > browserSize.height) y = y - ((y + contextSize.height) - browserSize.height) 155 | 156 | // Make context scrollable and start at the top of the browser 157 | // when context is higher than the browser 158 | if (contextSize.height > browserSize.height) { 159 | y = 0 160 | context.classList.add('basicContext--scrollable') 161 | } 162 | 163 | // Calculate the relative position of the mouse to the context 164 | let rx = normalizedEvent.x - x, 165 | ry = normalizedEvent.y - y 166 | 167 | return { x, y, rx, ry } 168 | 169 | } 170 | 171 | const bind = function(item = {}) { 172 | 173 | if (item.fn==null) return false 174 | if (item.visible===false) return false 175 | if (item.disabled===true) return false 176 | 177 | dom(`td[data-num='${ item.num }']`).onclick = item.fn 178 | dom(`td[data-num='${ item.num }']`).oncontextmenu = item.fn 179 | 180 | return true 181 | 182 | } 183 | 184 | const show = function(items, e, fnClose, fnCallback) { 185 | 186 | // Build context 187 | let html = build(items) 188 | 189 | // Add context to the body 190 | document.body.insertAdjacentHTML('beforeend', html) 191 | 192 | // Save current overflow and block scrolling of site 193 | if (overflow==null) { 194 | overflow = document.body.style.overflow 195 | document.body.style.overflow = 'hidden' 196 | } 197 | 198 | // Cache the context 199 | let context = dom() 200 | 201 | // Calculate position 202 | let position = getPosition(e, context) 203 | 204 | // Set position 205 | context.style.left = `${ position.x }px` 206 | context.style.top = `${ position.y }px` 207 | context.style.transformOrigin = `${ position.rx }px ${ position.ry }px` 208 | context.style.opacity = 1 209 | 210 | // Close fn fallback 211 | if (fnClose==null) fnClose = close 212 | 213 | // Bind click on background 214 | context.parentElement.onclick = fnClose 215 | context.parentElement.oncontextmenu = fnClose 216 | 217 | // Bind click on items 218 | items.forEach(bind) 219 | 220 | // Do not trigger default event or further propagation 221 | if (typeof e.preventDefault === 'function') e.preventDefault() 222 | if (typeof e.stopPropagation === 'function') e.stopPropagation() 223 | 224 | // Call callback when a function 225 | if (typeof fnCallback === 'function') fnCallback() 226 | 227 | return true 228 | 229 | } 230 | 231 | const visible = function() { 232 | 233 | let elem = dom() 234 | 235 | if (elem==null || elem.length===0) return false 236 | else return true 237 | 238 | } 239 | 240 | const close = function() { 241 | 242 | if (visible()===false) return false 243 | 244 | let container = document.querySelector('.basicContextContainer') 245 | 246 | container.parentElement.removeChild(container) 247 | 248 | // Reset overflow to its original value 249 | if (overflow!=null) { 250 | document.body.style.overflow = overflow 251 | overflow = null 252 | } 253 | 254 | return true 255 | 256 | } 257 | 258 | return { 259 | ITEM, 260 | SEPARATOR, 261 | show, 262 | visible, 263 | close 264 | } -------------------------------------------------------------------------------- /src/styles/_container.scss: -------------------------------------------------------------------------------- 1 | .basicContextContainer { 2 | 3 | position: fixed; 4 | width: 100%; 5 | height: 100%; 6 | top: 0; 7 | left: 0; 8 | z-index: $basicContext__zIndex; 9 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 10 | 11 | } -------------------------------------------------------------------------------- /src/styles/_context.scss: -------------------------------------------------------------------------------- 1 | .basicContext { 2 | 3 | position: absolute; 4 | opacity: 0; 5 | -moz-user-select: none; 6 | -webkit-user-select: none; 7 | user-select: none; 8 | 9 | box-sizing: border-box; 10 | * { box-sizing: border-box; } 11 | 12 | // Item -------------------------------------------------------------- // 13 | &__item { 14 | cursor: pointer; 15 | 16 | &--separator { 17 | float: left; 18 | width: 100%; 19 | height: 1px; 20 | cursor: default; 21 | } 22 | 23 | &--disabled { 24 | cursor: default; 25 | } 26 | } 27 | 28 | &__data { 29 | min-width: 140px; 30 | padding-right: 20px; 31 | text-align: left; 32 | white-space: nowrap; 33 | } 34 | 35 | &__icon { 36 | display: inline-block; 37 | } 38 | 39 | // Scrollable -------------------------------------------------------- // 40 | &--scrollable { 41 | height: 100%; 42 | -webkit-overflow-scrolling: touch; 43 | overflow-y: auto; 44 | } 45 | 46 | &--scrollable &__data { 47 | min-width: 160px; 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /src/styles/_vars.scss: -------------------------------------------------------------------------------- 1 | $basicContext__color : #4393e6 !default; 2 | $basicContext__zIndex : 1000 !default; -------------------------------------------------------------------------------- /src/styles/addons/fadein.scss: -------------------------------------------------------------------------------- 1 | // Effect --------------------------------------------------------- // 2 | @keyframes basicContext__fadeIn { 3 | 0% { opacity: 0; } 4 | 100% { opacity: 1; } 5 | } 6 | 7 | // Styles --------------------------------------------------------- // 8 | .basicContext { 9 | 10 | animation: basicContext__fadeIn .1s ease; 11 | 12 | } -------------------------------------------------------------------------------- /src/styles/addons/popin.scss: -------------------------------------------------------------------------------- 1 | // Effect --------------------------------------------------------- // 2 | @keyframes basicContext__popIn { 3 | 0% { transform: scale(0); } 4 | 100% { transform: scale(1); } 5 | } 6 | 7 | // Styles --------------------------------------------------------- // 8 | .basicContext { 9 | 10 | animation: basicContext__popIn .3s cubic-bezier(.51, .92, .24, 1.2); 11 | 12 | } -------------------------------------------------------------------------------- /src/styles/main.scss: -------------------------------------------------------------------------------- 1 | // Vars -------------------------------------------------------------- // 2 | @import 'vars'; 3 | 4 | // Layout ------------------------------------------------------------ // 5 | @import 'container'; 6 | @import 'context'; -------------------------------------------------------------------------------- /src/styles/themes/bright.scss: -------------------------------------------------------------------------------- 1 | // Vars -------------------------------------------------------------- // 2 | @import '../vars'; 3 | 4 | // Bright Theme ------------------------------------------------------- // 5 | .basicContext { 6 | 7 | padding: 6px 0; 8 | background-color: white; 9 | box-shadow: 0 2px 15px rgba(0, 0, 0, .2), 0 0 0 1px #eee; 10 | border-radius: 3px; 11 | 12 | &__item { 13 | margin-bottom: 2px; 14 | 15 | &--separator { 16 | margin: 4px 0; 17 | background-color: #eee; 18 | } 19 | 20 | &--disabled { 21 | opacity: .5; 22 | } 23 | 24 | &:last-child { 25 | margin-bottom: 0; 26 | } 27 | } 28 | 29 | &__data { 30 | padding: 6px 14px; 31 | color: #666; 32 | } 33 | 34 | &__item:not(.basicContext__item--disabled):hover &__data { 35 | color: $basicContext__color; 36 | background-color: rgba($basicContext__color, .1); 37 | } 38 | 39 | &__item:not(.basicContext__item--disabled):active &__data { 40 | color: darken($basicContext__color, 10%); 41 | background-color: rgba($basicContext__color, .15); 42 | } 43 | 44 | &__icon { 45 | margin-right: 8px; 46 | width: 12px; 47 | text-align: center; 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /src/styles/themes/dark.scss: -------------------------------------------------------------------------------- 1 | // Vars -------------------------------------------------------------- // 2 | @import '../vars'; 3 | 4 | // Dark Theme ------------------------------------------------------- // 5 | .basicContext { 6 | 7 | padding: 6px 0; 8 | background-color: rgba(40, 40, 40, 0.98); 9 | box-shadow: 0 2px 15px rgba(0, 0, 0, .4), 0 0 0 1px #222; 10 | border-radius: 3px; 11 | 12 | &__item { 13 | margin-bottom: 2px; 14 | 15 | &--separator { 16 | margin: 4px 0; 17 | background-color: #222; 18 | } 19 | 20 | &--disabled { 21 | opacity: .5; 22 | } 23 | 24 | &:last-child { 25 | margin-bottom: 0; 26 | } 27 | } 28 | 29 | &__data { 30 | padding: 6px 14px; 31 | color: white; 32 | } 33 | 34 | &__item:not(.basicContext__item--disabled):hover &__data { 35 | background-color: $basicContext__color; 36 | } 37 | 38 | &__item:not(.basicContext__item--disabled):active &__data { 39 | background-color: darken($basicContext__color, 10%); 40 | } 41 | 42 | &__icon { 43 | margin-right: 8px; 44 | width: 12px; 45 | text-align: center; 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /src/styles/themes/default.scss: -------------------------------------------------------------------------------- 1 | // Vars -------------------------------------------------------------- // 2 | @import '../vars'; 3 | 4 | // Default Theme ----------------------------------------------------- // 5 | .basicContext { 6 | 7 | padding: 6px; 8 | background-color: white; 9 | box-shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 0 1px rgba(0, 0, 0, .2); 10 | border-radius: 3px; 11 | 12 | &__item { 13 | margin-bottom: 2px; 14 | 15 | &--separator { 16 | margin: 4px 0; 17 | background-color: rgba(0, 0, 0, .1); 18 | } 19 | 20 | &--disabled { 21 | opacity: .5; 22 | } 23 | 24 | &:last-child { 25 | margin-bottom: 0; 26 | } 27 | } 28 | 29 | &__data { 30 | padding: 6px 8px; 31 | color: #333; 32 | border-radius: 2px; 33 | } 34 | 35 | &__item:not(.basicContext__item--disabled):hover &__data { 36 | color: white; 37 | background-color: $basicContext__color; 38 | } 39 | 40 | &__item:not(.basicContext__item--disabled):active &__data { 41 | background-color: darken($basicContext__color, 10%); 42 | } 43 | 44 | &__icon { 45 | margin-right: 10px; 46 | width: 12px; 47 | text-align: center; 48 | } 49 | 50 | } --------------------------------------------------------------------------------