├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Gruntfile.js ├── README.md ├── angularDrop.js ├── bower.json ├── docs ├── css │ └── style.css └── nav.html ├── karma-jqlite.conf.js ├── karma-jquery.conf.js ├── karma-shared.conf.js ├── lib └── grunt │ ├── plugins.js │ └── utils.js ├── logo.png ├── misc ├── art │ └── logo.idraw ├── changelog.tpl.md ├── demo │ ├── css │ │ ├── app.css │ │ ├── bootstrap.min.css │ │ └── font-awesome.min.css │ ├── font │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ └── fontawesome-webfont.woff │ ├── img │ │ ├── appbar.location.round.png │ │ └── map.jpg │ ├── index.html │ └── js │ │ ├── angular.min.js │ │ ├── app.js │ │ ├── bootstrap.min.js │ │ └── jquery.min.js └── validate-commit-msg.js ├── package.json ├── scripts └── travis │ └── init_logs.sh ├── src ├── dndDOM.js ├── draggable.js ├── drop.prefix ├── drop.suffix ├── droppable.js ├── provider.js ├── public.js ├── publish.js └── utils.js └── test ├── directive ├── draggable.spec.js └── droppable.spec.js ├── jquery_alias.js ├── jquery_remove.js └── provider ├── dnd.spec.js ├── drag.spec.js └── drop.spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /bower_components 3 | /node_modules 4 | /coverage 5 | /.bower-release 6 | /.grunt 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.1 4 | env: 5 | global: 6 | secure: PPm5YZ1XHLUnuUSbiqpaOw4LZd8r/cWtxAozKzyE9NVmIe229Otld1QzECrtSK58JnDOd7MinNuLswcl2MLWcgQJ2Bfh522psnzsotRYul8luymUtBO/a0BsvqryYXp3+dsZoV8grHbOVS3ezPcnrIXwIFATu3xuomBV0YuYntg= 7 | install: 8 | - npm config set spin false 9 | - npm config set loglevel http 10 | - npm install 11 | - npm install -g grunt-cli 12 | - npm install karma-coveralls 13 | - grunt bower 14 | before_script: 15 | - scripts/travis/init_logs.sh 16 | - export DISPLAY=:99.0 17 | - sh -e /etc/init.d/xvfb start 18 | script: 19 | - grunt test 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caitp/angular-drop/be9c9569b71aeb00043a4c51931a00d5783c2bb2/CHANGELOG.md -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | var files = require('./angularDrop').files; 2 | var util = require('./lib/grunt/utils'); 3 | var path = require('path'); 4 | 5 | module.exports = function(grunt) { 6 | 7 | grunt.loadNpmTasks('grunt-contrib-concat'); 8 | grunt.loadNpmTasks('grunt-contrib-clean'); 9 | grunt.loadNpmTasks('grunt-contrib-copy'); 10 | grunt.loadNpmTasks('grunt-contrib-connect'); 11 | grunt.loadNpmTasks('grunt-contrib-compress'); 12 | grunt.loadNpmTasks('grunt-ddescribe-iit'); 13 | grunt.loadNpmTasks('grunt-merge-conflict'); 14 | grunt.loadNpmTasks('grunt-parallel'); 15 | grunt.loadNpmTasks('grunt-shell'); 16 | grunt.loadNpmTasks('grunt-contrib-jshint'); 17 | grunt.loadTasks('lib/grunt'); 18 | grunt.loadNpmTasks('grunt-conventional-changelog'); 19 | grunt.loadNpmTasks('grunt-ngdocs-caitp'); 20 | grunt.loadNpmTasks('grunt-gh-pages'); 21 | grunt.loadNpmTasks('grunt-bower-release'); 22 | 23 | var DROP_VERSION = util.getVersion(); 24 | var dist = 'angular-drop-' + DROP_VERSION.full; 25 | 26 | 27 | // global beforeEach 28 | util.init(); 29 | 30 | var pkg = grunt.file.readJSON('package.json'); 31 | 32 | grunt.initConfig({ 33 | DROP_VERSION: DROP_VERSION, 34 | 35 | pkg: pkg, 36 | 37 | bowerRelease: { 38 | stable: { 39 | options: { 40 | endpoint: 'https://github.com/caitp/angular-drop-bower.git', 41 | packageName: 'angular-drop', 42 | stageDir: '.bower-release/', 43 | main: 'angular-drop.min.js', 44 | branchName: 'master', 45 | dependencies: {} 46 | }, 47 | files: [ 48 | { 49 | expand: true, 50 | cwd: 'build/', 51 | src: ['angular-drop.js', 'angular-drop.min.js', 'angular-drop.min.js.map'] 52 | } 53 | ] 54 | } 55 | }, 56 | 57 | parallel: { 58 | travis: { 59 | tasks: [ 60 | util.parallelTask(['test:unit'], {stream: true}), 61 | ] 62 | } 63 | }, 64 | 65 | connect: { 66 | devserver: { 67 | options: { 68 | port: 8000, 69 | hostname: '0.0.0.0', 70 | base: 'build/', 71 | keepalive: true, 72 | middleware: function(connect, options) { 73 | var doc_root = path.resolve(options.base); 74 | return [ 75 | connect.logger('dev'), 76 | connect.static(doc_root), 77 | connect.directory(doc_root) 78 | ]; 79 | } 80 | } 81 | }, 82 | testserver: { 83 | options: { 84 | port: 8000, 85 | hostname: '0.0.0.0', 86 | middleware: function(connect, options) { 87 | return [ 88 | function(req, res, next) { 89 | // cache GET requests to speed up tests on travis 90 | if (req.method === 'GET') { 91 | res.setHeader('Cache-control', 'public, max-age=3600'); 92 | } 93 | 94 | nest(); 95 | }, 96 | 97 | connect.static(options.base) 98 | ]; 99 | } 100 | } 101 | } 102 | }, 103 | 104 | tests: { 105 | jqlite: 'karma-jqlite.conf.js', 106 | jquery: 'karma-jquery.conf.js' 107 | }, 108 | 109 | autotest: { 110 | jqlite: 'karma-jqlite.conf.js', 111 | jquery: 'karma-jquery.conf.js' 112 | }, 113 | 114 | clean: { 115 | build: ['build'], 116 | tmp: ['tmp'] 117 | }, 118 | 119 | copy: { 120 | demohtml: { 121 | options: { 122 | // process html files with gruntfile config 123 | processContent: grunt.template.process 124 | }, 125 | files: [{ 126 | expand: true, 127 | src: ["**/*.html"], 128 | cwd: "misc/demo/", 129 | dest: "build/" 130 | }] 131 | }, 132 | demoassets: { 133 | files: [{ 134 | expand: true, 135 | // Don't re-copy html files, we process those 136 | src: ["**/**/*", "!**/*.html"], 137 | cwd: "misc/demo", 138 | dest: "build/" 139 | }] 140 | } 141 | }, 142 | 143 | jshint: { 144 | drop: { 145 | files: { src: files['angularDropSrc'] }, 146 | options: { jshintrc: 'src/.jshintrc' } 147 | } 148 | }, 149 | 150 | build: { 151 | drop: { 152 | dest: 'build/angular-drop.js', 153 | src: util.wrap([files['angularDropSrc']], 'drop'), 154 | } 155 | }, 156 | 157 | min: { 158 | drop: 'build/angular-drop.js' 159 | }, 160 | 161 | /** 162 | * TODO: Provide documentation! 163 | * docs: { 164 | * process: ['build/docs/*.html', 'build/docs/.htaccess'] 165 | * } 166 | */ 167 | 168 | "ddescribe-iit": { 169 | files: [ 170 | 'test/**/*.js', 171 | ] 172 | }, 173 | 174 | "merge-conflict": { 175 | files: [ 176 | 'src/**/*', 177 | 'test/**/*', 178 | 'docs/**/*', 179 | 'css/**/*' 180 | ] 181 | }, 182 | 183 | compress: { 184 | build: { 185 | options: {archive: 'build/' + dist + '.zip', mode: 'zip'}, 186 | src: ['**'], cwd: 'build', expand: true, dot: true, dest: dist + '/' 187 | } 188 | }, 189 | 190 | write: { 191 | versionTXT: { file: 'build/version.txt', val: DROP_VERSION.full }, 192 | versionJSON: { file: 'build/version.json', val: JSON.stringify(DROP_VERSION) } 193 | }, 194 | 195 | 'gh-pages': { 196 | 'gh-pages': { 197 | options: { 198 | base: 'build', 199 | repo: 'https://github.com/caitp/angular-drop.git', 200 | message: 'gh-pages v<%= pkg.version %>', 201 | add: false 202 | }, 203 | src: ['**/*'] 204 | } 205 | }, 206 | 207 | ngdocs: { 208 | options: { 209 | dest: "build/docs", 210 | scripts: [ 211 | 'angular.js' 212 | ], 213 | styles: [ 214 | 'docs/css/style.css' 215 | ], 216 | navTemplate: 'docs/nav.html', 217 | title: 'AngularDrop', 218 | image: 'logo.png', 219 | imageLink: 'http://caitp.github.io/angular-drop', 220 | titleLink: 'http://caitp.github.io/angular-drop', 221 | html5Mode: false, 222 | analytics: { 223 | account: 'UA-44389518-1', 224 | domainName: 'caitp.github.io' 225 | } 226 | }, 227 | api: { 228 | src: ["src/**/*.js", "src/**/*.ngdoc"], 229 | title: "API Documentation" 230 | } 231 | } 232 | }); 233 | 234 | // alias tasks 235 | grunt.registerTask('test', 'Run unit tests with Karma', ['package', 'test:unit']); 236 | grunt.registerTask('test:jqlite', 'Run the unit tests with Karma (jqLite only)', ['tests:jqlite']); 237 | grunt.registerTask('test:jquery', 'Run the unit tests with Karma (jQuery only)', ['tests:jquery']); 238 | grunt.registerTask('test:unit', 'Run jqLite and jQuery unit tests with Karma', ['tests:jqlite', 'tests:jquery']); 239 | 240 | grunt.registerTask('minify', ['bower','clean', 'build', 'minall']); 241 | grunt.registerTask('webserver', ['connect:devserver']); 242 | grunt.registerTask('package', ['bower','clean', 'buildall', 'minall', 'collect-errors', 'write', 'compress', 'copy']); 243 | grunt.registerTask('package-without-bower', ['clean', 'buildall', 'minall', 'collect-errors', 'write', 'compress']); 244 | grunt.registerTask('ci-checks', ['ddescribe-iit', 'merge-conflict', 'jshint']); 245 | grunt.registerTask('default', ['package']); 246 | 247 | grunt.registerTask('enforce', 'Install commit message enforce script if it doesn\'t exist', 248 | function() { 249 | if (!grunt.file.exists('.git/hooks/commit-msg')) { 250 | grunt.file.copy('misc/validate-commit-msg.js', '.git/hooks/commit-msg'); 251 | require('fs').chmodSync('.git/hooks/commit-msg', '0755'); 252 | } 253 | }); 254 | 255 | // Shell commands 256 | grunt.registerMultiTask('shell', 'Run shell commands', function() { 257 | var self = this, sh = require('shelljs'); 258 | self.data.forEach(function(cmd) { 259 | cmd = cmd.replace('%version%', grunt.file.readJSON('package.json').version); 260 | cmd = cmd.replace('%PATCHTYPE%', grunt.option('patch') && 'patch' || 261 | grunt.option('major') && 262 | 'major' || 'minor'); 263 | grunt.log.ok(cmd); 264 | var result = sh.exec(cmd, {silent: true }); 265 | if (result.code !== 0) { 266 | grunt.fatal(result.output); 267 | } 268 | }); 269 | }); 270 | 271 | // Version management 272 | function setVersion(type, suffix) { 273 | var file = 'package.json', 274 | VERSION_REGEX = /([\'|\"]version[\'|\"][ ]*:[ ]*[\'|\"])([\d|.]*)(-\w+)*([\'|\"])/, 275 | contents = grunt.file.read(file), 276 | version; 277 | contents = contents.replace(VERSION_REGEX, function(match, left, center) { 278 | version = center; 279 | if (type) { 280 | version = require('semver').inc(version, type); 281 | } 282 | // semver.inc strips suffix, if it existed 283 | if (suffix) { 284 | version += '-' + suffix; 285 | } 286 | return left + version + '"'; 287 | }); 288 | grunt.log.ok('Version set to ' + version.cyan); 289 | grunt.file.write(file, contents); 290 | return version; 291 | } 292 | 293 | grunt.registerTask('version', 'Set version. If no arguments, it just takes off suffix', 294 | function() { 295 | setVersion(this.args[0], this.args[1]); 296 | }); 297 | 298 | grunt.registerTask('docgen', function() { 299 | var self = this; 300 | if (typeof self.args[0] === 'string') { 301 | grunt.config('pkg.version', self.args[0]); 302 | } 303 | grunt.task.mark().run('gh-pages'); 304 | }); 305 | 306 | grunt.registerTask('release', 'Release on bower', ['build', 'bowerRelease']); 307 | 308 | return grunt; 309 | }; 310 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## AngularDrop [![Build Status](https://travis-ci.org/caitp/angular-drop.svg?branch=master)](https://travis-ci.org/caitp/angular-drop) [![devDependency Status](https://david-dm.org/caitp/angular-drop/dev-status.svg)](https://david-dm.org/caitp/angular-drop#info=devDependencies) [![Coverage Status](http://img.shields.io/coveralls/caitp/angular-drop/master.svg)](https://coveralls.io/r/caitp/angular-drop?branch=master) 2 | 3 | ### Drag & Drop functionality in AngularJS, no jQuery required 4 | 5 | [Demo](http://caitp.github.io/angular-drop) | [Docs](http://caitp.github.io/angular-drop/docs) 6 | 7 | AngularDrop provides simple building blocks for Drag & Drop functionality in AngularJS. 8 | 9 | Drag & Drop is a fairly complex user interaction, even though it might seem trivial initially. It is further complicated in AngularJS by the concept of scopes, which can have a major impact on the content of a drag/dropped node. 10 | 11 | I do not claim to have the answers to any or all of the questions raised by implementing drag & drop functionality in a complex application, but I am interested in finding out so that we can deliver the best experience possible to users of our apps. 12 | 13 | With the best intentions, it is hoped that we will be able to deliver: 14 | 15 | - Full support for mobile applications 16 | - Cleverly handling scope-changing 17 | - Scope events fired when elements are dragged and dropped 18 | - Support for dragging and dropping between nested browsing contexts 19 | - Support for dragging and dropping between different windows 20 | 21 | This library is not simply a pair of directives, but is in fact also building blocks for creating custom directives with specialized Drag & Drop behaviour. 22 | 23 | ### Installation 24 | 25 | Angular-drop is now registered on bower! You can install, as you'd expect, like so: 26 | 27 | ```bash 28 | bower install angular-drop --save 29 | ``` 30 | 31 | If you prefer to get the package using more traditional means, the gh-pages branch contains everything you'd need. You can get at those [here](https://github.com/caitp/angular-drop/tree/gh-pages) 32 | 33 | 34 | ### Contributing 35 | 36 | I'd be grateful for any form of contribution, whether it be the creation of demo pages, bug reports, documentation, feature requests, or above all else, patches. 37 | 38 | Patches should follow the [Google JavaScript Style Guide](https://google.github.io/styleguide/javascriptguide.xml), and each and every new feature or bug fix should incorporate one or more meaningful tests to assist in preventing future regressions. 39 | 40 | While you may, if you so wish, discuss this module anywhere you like, I will be most likely to respond to inquiries directed to me on IRC (particularly in #angularjs on irc.freenode.net), or on the [issue tracker](https://github.com/caitp/angular-drop/issues). 41 | 42 | ### License 43 | 44 | The MIT License (MIT) 45 | 46 | Copyright (c) 2013 Caitlin Potter & Contributors 47 | 48 | Permission is hereby granted, free of charge, to any person obtaining a copy 49 | of this software and associated documentation files (the "Software"), to deal 50 | in the Software without restriction, including without limitation the rights 51 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 52 | copies of the Software, and to permit persons to whom the Software is 53 | furnished to do so, subject to the following conditions: 54 | 55 | The above copyright notice and this permission notice shall be included in 56 | all copies or substantial portions of the Software. 57 | 58 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 59 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 60 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 61 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 62 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 63 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 64 | THE SOFTWARE. 65 | -------------------------------------------------------------------------------- /angularDrop.js: -------------------------------------------------------------------------------- 1 | angularDropFiles = { 2 | 'angularDropSrc': [ 3 | 'src/utils.js', 4 | 'src/dndDOM.js', 5 | 'src/draggable.js', 6 | 'src/droppable.js', 7 | 'src/provider.js', 8 | 'src/public.js' 9 | ], 10 | 11 | 'angularDropTest': [ 12 | 'test/**/*.spec.js', 13 | ], 14 | 15 | 'karma': [ 16 | 'bower_components/jquery/dist/jquery.js', 17 | 'test/jquery_remove.js', 18 | 'bower_components/angular/angular.js', 19 | 'bower_components/angular-mocks/angular-mocks.js', 20 | '@angularDropSrc', 21 | 'src/publish.js', 22 | '@angularDropTest', 23 | ], 24 | 25 | 'karmaExclude': [ 26 | 'test/jquery_alias.js', 27 | ], 28 | 29 | 'karmaJquery': [ 30 | 'bower_components/jquery/dist/jquery.js', 31 | 'bower_components/angular/angular.js', 32 | 'bower_components/angular-mocks/angular-mocks.js', 33 | 'test/jquery_alias.js', 34 | '@angularDropSrc', 35 | 'src/publish.js', 36 | '@angularDropTest', 37 | ], 38 | 39 | 'karmaJqueryExclude': [ 40 | 'test/jquery_remove.js' 41 | ] 42 | }; 43 | 44 | if (exports) { 45 | exports.files = angularDropFiles; 46 | exports.mergeFilesFor = function() { 47 | var files = []; 48 | 49 | Array.prototype.slice.call(arguments, 0).forEach(function(filegroup) { 50 | angularDropFiles[filegroup].forEach(function(file) { 51 | // replace @ref 52 | var match = file.match(/^\@(.*)/); 53 | if (match) { 54 | files = files.concat(angularDropFiles[match[1]]); 55 | } else { 56 | files.push(file); 57 | } 58 | }); 59 | }); 60 | 61 | return files; 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-drop", 3 | "version": "0.0.1", 4 | "authors": [ 5 | "Caitlin Potter " 6 | ], 7 | "license": "MIT", 8 | "ignore": [ 9 | "**/.*", 10 | "node_modules", 11 | "bower_components", 12 | "test", 13 | "tests" 14 | ], 15 | "dependencies": { 16 | "jquery": "~2.1.3", 17 | "google-code-prettify": "1.0.0", 18 | "closure-compiler": "https://closure-compiler.googlecode.com/files/compiler-20130603.zip", 19 | "ng-closure-runner": "https://raw.github.com/angular/ng-closure-runner/v0.2.2/assets/ng-closure-runner.zip", 20 | "angular": "1.3.15", 21 | "angular-mocks": "1.3.15", 22 | "angular-drop": "~0.0.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /docs/css/style.css: -------------------------------------------------------------------------------- 1 | .bs-docs-social { 2 | margin-top: 1em; 3 | padding: 15px 0; 4 | text-align: center; 5 | background-color: rgba(245,245,245,0.3); 6 | border-top: 1px solid rgba(255,255,255,0.3); 7 | border-bottom: 1px solid rgba(221,221,221,0.3); 8 | } 9 | .bs-docs-social-buttons { 10 | position: absolute; 11 | top: 8px; 12 | margin-left: 0; 13 | margin-bottom: 0; 14 | padding-left: 0; 15 | list-style: none; 16 | } 17 | .bs-docs-social-buttons li { 18 | list-style: none; 19 | display: inline-block; 20 | line-height: 1; 21 | } 22 | -------------------------------------------------------------------------------- /docs/nav.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | Download (<%= pkg.version%>) 4 | 5 |
6 |
7 | 13 | 14 | -------------------------------------------------------------------------------- /karma-jqlite.conf.js: -------------------------------------------------------------------------------- 1 | var dropFiles = require('./angularDrop'); 2 | var sharedConfig = require('./karma-shared.conf'); 3 | 4 | module.exports = function(config) { 5 | sharedConfig(config, {testName: 'AngularDrop: jqLite', logFile: 'karma-jqlite.log'}); 6 | 7 | config.set({ 8 | files: dropFiles.mergeFilesFor('karma'), 9 | exclude: dropFiles.mergeFilesFor('karmaExclude'), 10 | 11 | junitReporter: { 12 | outputFile: 'test_out/jqlite.xml', 13 | suite: 'jqLite' 14 | } 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /karma-jquery.conf.js: -------------------------------------------------------------------------------- 1 | var dropFiles = require('./angularDrop'); 2 | var sharedConfig = require('./karma-shared.conf'); 3 | 4 | module.exports = function(config) { 5 | sharedConfig(config, {testName: 'AngularDrop: jQuery', logFile: 'karma-jquery.log'}); 6 | 7 | config.set({ 8 | files: dropFiles.mergeFilesFor('karmaJquery'), 9 | exclude: dropFiles.mergeFilesFor('karmaJqueryExclude'), 10 | 11 | junitReporter: { 12 | outputFile: 'test_out/jquery.xml', 13 | suite: 'jQuery' 14 | } 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /karma-shared.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config, specificOptions) { 2 | config.set({ 3 | frameworks: ['jasmine'], 4 | autoWatch: true, 5 | logLevel: config.LOG_INFO, 6 | logColors: true, 7 | browsers: ['Firefox', 'PhantomJS'], 8 | browserDisconnectTimeout: 5000, 9 | reporters: ['dots', 'coverage'], 10 | preprocessors: { 11 | 'src/*.js': ['coverage'] 12 | }, 13 | 14 | coverageReporter: { 15 | reporters: [ 16 | { 17 | type: 'lcov', 18 | dir: 'coverage/' 19 | }, 20 | { 21 | type: 'text' 22 | } 23 | ] 24 | }, 25 | 26 | 27 | // config for Travis CI 28 | sauceLabs: { 29 | testName: specificOptions.testName || 'AngularDrop', 30 | startConnect: false, 31 | tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER 32 | }, 33 | 34 | // For more browsers on Sauce Labs see: 35 | // https://saucelabs.com/docs/platforms/webdriver 36 | customLaunchers: { 37 | 'SL_Chrome': { 38 | base: 'SauceLabs', 39 | browserName: 'chrome' 40 | }, 41 | 'SL_Firefox': { 42 | base: 'SauceLabs', 43 | browserName: 'firefox' 44 | }, 45 | 'SL_Safari': { 46 | base: 'SauceLabs', 47 | browserName: 'safari', 48 | platform: 'Mac 10.8', 49 | version: '6' 50 | }, 51 | 'SL_IE_8': { 52 | base: 'SauceLabs', 53 | browserName: 'internet explorer', 54 | platform: 'Windows 7', 55 | version: '8' 56 | }, 57 | 'SL_IE_9': { 58 | base: 'SauceLabs', 59 | browserName: 'internet explorer', 60 | platform: 'Windows 2008', 61 | version: '9' 62 | }, 63 | 'SL_IE_10': { 64 | base: 'SauceLabs', 65 | browserName: 'internet explorer', 66 | platform: 'Windows 2012', 67 | version: '10' 68 | } 69 | } 70 | }); 71 | 72 | function arrayRemove(array, item) { 73 | var index = array.indexOf(item); 74 | if (index >= 0) array.splice(index, 1); 75 | } 76 | 77 | if (process.argv.indexOf('--debug') >= 0) { 78 | arrayRemove(config.reporters, 'coverage'); 79 | for (var key in config.preprocessors) { 80 | arrayRemove(config.preprocessors[key], 'coverage'); 81 | } 82 | } 83 | 84 | if (process.env.TRAVIS) { 85 | // TODO(vojta): remove once SauceLabs supports websockets. 86 | // This speeds up the capturing a bit, as browsers don't even try to use websocket. 87 | config.transports = ['xhr-polling']; 88 | config.reporters.push('coveralls'); 89 | 90 | // Debug logging into a file, that we print out at the end of the build. 91 | config.loggers.push({ 92 | type: 'file', 93 | filename: 'logs/' + (specificOptions.logFile || 'karma.log'), 94 | level: config.LOG_DEBUG 95 | }); 96 | } 97 | }; 98 | -------------------------------------------------------------------------------- /lib/grunt/plugins.js: -------------------------------------------------------------------------------- 1 | var bower = require('bower'); 2 | var util = require('./utils.js'); 3 | var spawn = require('child_process').spawn; 4 | 5 | module.exports = function(grunt) { 6 | 7 | grunt.registerMultiTask('min', 'minify JS files', function(){ 8 | util.min.call(util, this.data, this.async()); 9 | }); 10 | 11 | 12 | grunt.registerTask('minall', 'minify all the JS files in parallel', function(){ 13 | var files = grunt.config('min'); 14 | files = Object.keys(files).map(function(key){ return files[key]; }); 15 | grunt.util.async.forEach(files, util.min.bind(util), this.async()); 16 | }); 17 | 18 | 19 | grunt.registerMultiTask('build', 'build JS files', function(){ 20 | util.build.call(util, this.data, this.async()); 21 | }); 22 | 23 | 24 | grunt.registerTask('buildall', 'build all the JS files in parallel', function(){ 25 | var builds = grunt.config('build'); 26 | builds = Object.keys(builds).map(function(key){ return builds[key]; }); 27 | grunt.util.async.forEach(builds, util.build.bind(util), this.async()); 28 | }); 29 | 30 | 31 | grunt.registerMultiTask('write', 'write content to a file', function(){ 32 | grunt.file.write(this.data.file, this.data.val); 33 | grunt.log.ok('wrote to ' + this.data.file); 34 | }); 35 | 36 | 37 | grunt.registerMultiTask('tests', '**Use `grunt test` instead**', function(){ 38 | util.startKarma.call(util, this.data, true, this.async()); 39 | }); 40 | 41 | 42 | grunt.registerMultiTask('autotest', 'Run and watch the unit tests with Karma', function(){ 43 | util.startKarma.call(util, this.data, false, this.async()); 44 | }); 45 | 46 | grunt.registerTask('collect-errors', 'Combine stripped error files', function () { 47 | util.collectErrors(); 48 | }); 49 | 50 | grunt.registerTask('bower', 'Install Bower packages.', function () { 51 | var done = this.async(); 52 | 53 | bower.commands.install() 54 | .on('log', function (result) { 55 | grunt.log.ok('bower: ' + result.id + ' ' + result.data.endpoint.name); 56 | }) 57 | .on('error', grunt.fail.warn.bind(grunt.fail)) 58 | .on('end', done); 59 | }); 60 | }; 61 | -------------------------------------------------------------------------------- /lib/grunt/utils.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var shell = require('shelljs'); 3 | var grunt = require('grunt'); 4 | var spawn = require('child_process').spawn; 5 | var version; 6 | var CSP_CSS_HEADER = '/* Include this file in your html if you are using the CSP mode. */\n\n'; 7 | 8 | function hasFlag(flag) { 9 | return process.argv.indexOf(flag) >= 0; 10 | } 11 | 12 | module.exports = { 13 | 14 | init: function() { 15 | if (!process.env.TRAVIS) { 16 | shell.exec('npm install'); 17 | } 18 | }, 19 | 20 | 21 | getVersion: function(){ 22 | if (version) return version; 23 | 24 | var package = JSON.parse(fs.readFileSync('package.json', 'UTF-8')); 25 | var match = package.version.match(/^([^\-]*)(?:\-(.+))?$/); 26 | var semver = match[1].split('.'); 27 | var hash = shell.exec('git rev-parse --short HEAD', {silent: true}).output.replace('\n', ''); 28 | if (hash === 'fatal: Needed a single revision') hash = 'snapshot'; 29 | var fullVersion = match[1]; 30 | 31 | if (match[2]) { 32 | fullVersion += '-'; 33 | fullVersion += (match[2] == 'snapshot') ? hash : match[2]; 34 | } 35 | 36 | version = { 37 | full: fullVersion, 38 | major: semver[0], 39 | minor: semver[1], 40 | dot: semver[2].replace(/rc\d+/, ''), 41 | codename: package.codename, 42 | cdn: package.cdnVersion 43 | }; 44 | 45 | return version; 46 | }, 47 | 48 | 49 | startKarma: function(config, singleRun, done){ 50 | var browsers = grunt.option('browsers'); 51 | var watch = hasFlag('--watch'); 52 | var reporters = grunt.option('reporters'); 53 | var noColor = grunt.option('no-colors'); 54 | var debug = hasFlag('--debug'); 55 | var port = grunt.option('port'); 56 | if (watch) singleRun = false; 57 | var args = ['node_modules/karma/bin/karma', 'start', config]; 58 | if (singleRun) args.push('--single-run=true'); 59 | if (watch) args.push('--autowatch'); 60 | if (reporters) args.push('--reporters=' + reporters); 61 | if (browsers) args.push('--browsers=' + browsers); 62 | if (noColor) args.push('--no-colors'); 63 | if (port) args.push('--port=' + port); 64 | if (debug) args.push('--debug'); 65 | var p = spawn('node', args); 66 | p.stdout.pipe(process.stdout); 67 | p.stderr.pipe(process.stderr); 68 | p.on('exit', function(code){ 69 | if(code !== 0) grunt.fail.warn("Karma test(s) failed. Exit code: " + code); 70 | done(); 71 | }); 72 | }, 73 | 74 | 75 | wrap: function(src, name){ 76 | src.unshift('src/' + name + '.prefix'); 77 | src.push('src/' + name + '.suffix'); 78 | return src; 79 | }, 80 | 81 | 82 | addStyle: function(src, styles, minify){ 83 | styles = styles.reduce(processCSS.bind(this), { 84 | js: [src], 85 | css: [] 86 | }); 87 | return { 88 | js: styles.js.join('\n'), 89 | css: styles.css.join('\n') 90 | }; 91 | 92 | function processCSS(state, file) { 93 | var css = fs.readFileSync(file).toString(), 94 | js; 95 | state.css.push(css); 96 | 97 | if(minify){ 98 | css = css 99 | .replace(/\r?\n/g, '') 100 | .replace(/\/\*.*?\*\//g, '') 101 | .replace(/:\s+/g, ':') 102 | .replace(/\s*\{\s*/g, '{') 103 | .replace(/\s*\}\s*/g, '}') 104 | .replace(/\s*\,\s*/g, ',') 105 | .replace(/\s*\;\s*/g, ';'); 106 | } 107 | //escape for js 108 | css = css 109 | .replace(/\\/g, '\\\\') 110 | .replace(/'/g, "\\'") 111 | .replace(/\r?\n/g, '\\n'); 112 | //js = "!angular.$$csp() && angular.element(document).find('head').prepend('');"; 113 | //state.js.push(js); 114 | 115 | return state; 116 | } 117 | }, 118 | 119 | 120 | process: function(src, DROP_VERSION, strict){ 121 | var processed = src 122 | .replace(/"DROP_VERSION_FULL"/g, DROP_VERSION.full) 123 | .replace(/"DROP_VERSION_MAJOR"/, DROP_VERSION.major) 124 | .replace(/"DROP_VERSION_MINOR"/, DROP_VERSION.minor) 125 | .replace(/"DROP_VERSION_DOT"/, DROP_VERSION.dot) 126 | .replace(/"DROP_VERSION_CDN"/, DROP_VERSION.cdn) 127 | .replace(/"DROP_VERSION_CODENAME"/, DROP_VERSION.codename); 128 | if (strict !== false) processed = this.singleStrict(processed, '\n\n', true); 129 | return processed; 130 | }, 131 | 132 | 133 | build: function(config, fn){ 134 | var files = grunt.file.expand(config.src); 135 | var styles = config.styles; 136 | var processedStyles; 137 | //concat 138 | var src = files.map(function(filepath) { 139 | return grunt.file.read(filepath); 140 | }).join(grunt.util.normalizelf('\n')); 141 | //process 142 | var processed = this.process(src, grunt.config('DROP_VERSION'), config.strict); 143 | if (styles) { 144 | processedStyles = this.addStyle(processed, styles.css, styles.minify); 145 | processed = processedStyles.js; 146 | if (config.styles.generateCspCssFile) { 147 | grunt.file.write(removeSuffix(config.dest) + '-csp.css', CSP_CSS_HEADER + processedStyles.css); 148 | } 149 | } 150 | //write 151 | grunt.file.write(config.dest, processed); 152 | grunt.log.ok('File ' + config.dest + ' created.'); 153 | fn(); 154 | 155 | function removeSuffix(fileName) { 156 | return fileName.replace(/\.js$/, ''); 157 | } 158 | }, 159 | 160 | 161 | singleStrict: function(src, insert){ 162 | return src 163 | .replace(/\s*("|')use strict("|');\s*/g, insert) // remove all file-specific strict mode flags 164 | .replace(/(\(function\([^)]*\)\s*\{)/, "$1'use strict';"); // add single strict mode flag 165 | }, 166 | 167 | 168 | sourceMap: function(mapFile, fileContents) { 169 | var sourceMapLine = '//# sourceMappingURL=' + mapFile + '\n'; 170 | return fileContents + sourceMapLine; 171 | }, 172 | 173 | 174 | min: function(file, done) { 175 | var classPathSep = (process.platform === "win32") ? ';' : ':'; 176 | var minFile = file.replace(/\.js$/, '.min.js'); 177 | var mapFile = minFile + '.map'; 178 | var mapFileName = mapFile.match(/[^\/]+$/)[0]; 179 | var errorFileName = file.replace(/\.js$/, '-errors.json'); 180 | var versionNumber = this.getVersion().number; 181 | shell.exec( 182 | 'java ' + 183 | this.java32flags() + ' ' + 184 | '-Xmx512M ' + 185 | '-cp bower_components/closure-compiler/compiler.jar' + classPathSep + 186 | 'bower_components/ng-closure-runner/ngcompiler.jar ' + 187 | 'org.angularjs.closurerunner.NgClosureRunner ' + 188 | '--compilation_level SIMPLE_OPTIMIZATIONS ' + 189 | '--language_in ECMASCRIPT5_STRICT ' + 190 | '--minerr_pass ' + 191 | '--minerr_errors ' + errorFileName + ' ' + 192 | '--minerr_url http://errors.angularjs.org/' + versionNumber + '/ ' + 193 | '--source_map_format=V3 ' + 194 | '--create_source_map ' + mapFile + ' ' + 195 | '--js ' + file + ' ' + 196 | '--js_output_file ' + minFile, 197 | function(code) { 198 | if (code !== 0) grunt.fail.warn('Error minifying ' + file); 199 | 200 | // closure creates the source map relative to build/ folder, we need to strip those references 201 | grunt.file.write(mapFile, grunt.file.read(mapFile).replace('"file":"build/', '"file":"'). 202 | replace('"sources":["build/','"sources":["')); 203 | 204 | // move add use strict into the closure + add source map pragma 205 | grunt.file.write(minFile, this.sourceMap(mapFileName, this.singleStrict(grunt.file.read(minFile), '\n'))); 206 | grunt.log.ok(file + ' minified into ' + minFile); 207 | done(); 208 | }.bind(this)); 209 | }, 210 | 211 | 212 | //returns the 32-bit mode force flags for java compiler if supported, this makes the build much faster 213 | java32flags: function(){ 214 | if (process.platform === "win32") return ''; 215 | if (shell.exec('java -version -d32 2>&1', {silent: true}).code !== 0) return ''; 216 | return ' -d32 -client'; 217 | }, 218 | 219 | 220 | //collects and combines error messages stripped out in minify step 221 | collectErrors: function () { 222 | var combined = { 223 | id: 'ng', 224 | generated: new Date().toString(), 225 | errors: {} 226 | }; 227 | grunt.file.expand('build/*-errors.json').forEach(function (file) { 228 | var errors = grunt.file.readJSON(file), 229 | namespace; 230 | Object.keys(errors).forEach(function (prop) { 231 | if (typeof errors[prop] === 'object') { 232 | namespace = errors[prop]; 233 | if (combined.errors[prop]) { 234 | Object.keys(namespace).forEach(function (code) { 235 | if (combined.errors[prop][code] && combined.errors[prop][code] !== namespace[code]) { 236 | grunt.warn('[collect-errors] Duplicate minErr codes don\'t match!'); 237 | } else { 238 | combined.errors[prop][code] = namespace[code]; 239 | } 240 | }); 241 | } else { 242 | combined.errors[prop] = namespace; 243 | } 244 | } else { 245 | if (combined.errors[prop] && combined.errors[prop] !== errors[prop]) { 246 | grunt.warn('[collect-errors] Duplicate minErr codes don\'t match!'); 247 | } else { 248 | combined.errors[prop] = errors[prop]; 249 | } 250 | } 251 | }); 252 | }); 253 | grunt.file.write('build/errors.json', JSON.stringify(combined)); 254 | grunt.file.expand('build/*-errors.json').forEach(grunt.file.delete); 255 | }, 256 | 257 | 258 | //csp connect middleware 259 | csp: function(){ 260 | return function(req, res, next){ 261 | res.setHeader("X-WebKit-CSP", "default-src 'self';"); 262 | res.setHeader("X-Content-Security-Policy", "default-src 'self'"); 263 | res.setHeader("Content-Security-Policy", "default-src 'self'"); 264 | next(); 265 | }; 266 | }, 267 | 268 | 269 | //rewrite connect middleware (required for html5Mode, which is not being used) 270 | rewrite: function(){ 271 | return function(req, res, next){ 272 | var REWRITE = /\/(guide|api|cookbook|misc|tutorial|error).*$/, 273 | IGNORED = /(\.(css|js|png|jpg)$|partials\/.*\.html$)/, 274 | match; 275 | 276 | if (!IGNORED.test(req.url) && (match = req.url.match(REWRITE))) { 277 | console.log('rewriting', req.url); 278 | req.url = req.url.replace(match[0], '/index.html'); 279 | } 280 | next(); 281 | }; 282 | }, 283 | 284 | parallelTask: function(args, options) { 285 | var task = { 286 | grunt: true, 287 | args: args, 288 | stream: options && options.stream 289 | }; 290 | 291 | args.push('--port=' + this.sauceLabsAvailablePorts.pop()); 292 | 293 | if (args.indexOf('test:e2e') !== -1 && grunt.option('e2e-browsers')) { 294 | args.push('--browsers=' + grunt.option('e2e-browsers')); 295 | } else if (grunt.option('browsers')) { 296 | args.push('--browsers=' + grunt.option('browsers')); 297 | } 298 | 299 | if (grunt.option('reporters')) { 300 | args.push('--reporters=' + grunt.option('reporters')); 301 | } 302 | 303 | return task; 304 | }, 305 | 306 | // see http://saucelabs.com/docs/connect#localhost 307 | sauceLabsAvailablePorts: [9000, 9001, 9080, 9090, 9876] 308 | }; 309 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caitp/angular-drop/be9c9569b71aeb00043a4c51931a00d5783c2bb2/logo.png -------------------------------------------------------------------------------- /misc/art/logo.idraw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caitp/angular-drop/be9c9569b71aeb00043a4c51931a00d5783c2bb2/misc/art/logo.idraw -------------------------------------------------------------------------------- /misc/changelog.tpl.md: -------------------------------------------------------------------------------- 1 | # <%= version%> (<%= today%>) 2 | <% if (_(changelog.feat).size() > 0) { %> 3 | ## Features 4 | <% _(changelog.feat).keys().sort().forEach(function(scope) { %> 5 | - **<%= scope%>:** <% changelog.feat[scope].forEach(function(change) { %> 6 | - <%= change.msg%> (<%= helpers.commitLink(change.sha1) %>) <% }); %><% }); %> <% } %> 7 | <% if (_(changelog.fix).size() > 0) { %> 8 | ## Bug Fixes 9 | <% _(changelog.fix).keys().sort().forEach(function(scope) { %> 10 | - **<%= scope%>:** <% changelog.fix[scope].forEach(function(change) { %> 11 | - <%= change.msg%> (<%= helpers.commitLink(change.sha1) %>) <% }); %><% }); %> <% } %> 12 | <% if (_(changelog.breaking).size() > 0) { %> 13 | ## Breaking Changes 14 | <% _(changelog.breaking).keys().sort().forEach(function(scope) { %> 15 | - **<%= scope%>:** <% changelog.breaking[scope].forEach(function(change) { %> 16 | <%= change.msg%><% }); %><% }); %> <% } %> 17 | -------------------------------------------------------------------------------- /misc/demo/css/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | opacity: 1; 4 | -webkit-transition: opacity 1s ease; 5 | -moz-transition: opacity 1s ease; 6 | transition: opacity 1s; 7 | padding-bottom: 10em; 8 | } 9 | 10 | .jumbotron { 11 | font-family: Arial, Helvetica, sans-serif; 12 | position: relative; 13 | padding: 40px 0; 14 | color: #fff; 15 | text-align: center; 16 | text-shadow: 0 1px 3px rgba(0,0,0,.4), 0 0 30px rgba(0,0,0,.075); 17 | background: #020031; 18 | background: -moz-linear-gradient(45deg, #020031 0%, #6d3353 100%); 19 | background: -webkit-gradient(linear, left bottom, right top, color-stop(0%,#020031), color-stop(100%,#6d3353)); 20 | background: -webkit-linear-gradient(45deg, #020031 0%,#6d3353 100%); 21 | background: -o-linear-gradient(45deg, #020031 0%,#6d3353 100%); 22 | background: -ms-linear-gradient(45deg, #020031 0%,#6d3353 100%); 23 | background: linear-gradient(45deg, #020031 0%,#6d3353 100%); 24 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#020031', endColorstr='#6d3353',GradientType=1 ); 25 | -webkit-box-shadow: inset 0 3px 7px rgba(0,0,0,.2), inset 0 -3px 7px rgba(0,0,0,.2); 26 | -moz-box-shadow: inset 0 3px 7px rgba(0,0,0,.2), inset 0 -3px 7px rgba(0,0,0,.2); 27 | box-shadow: inset 0 3px 7px rgba(0,0,0,.2), inset 0 -3px 7px rgba(0,0,0,.2); 28 | border-radius: 0; 29 | -moz-border-radius: 0; 30 | -webkit-border-radius: 0; 31 | -o-border-radius: 0; 32 | } 33 | .jumbotron .btn, .pagination-centered .btn { 34 | /*float: none;*/ 35 | font-weight: normal; 36 | } 37 | .jumbotron p { 38 | margin: 1em 0; 39 | } 40 | .bs-docs-social { 41 | margin-top: 1em; 42 | padding: 15px 0; 43 | text-align: center; 44 | background-color: rgba(245,245,245,0.3); 45 | border-top: 1px solid rgba(255,255,255,0.3); 46 | border-bottom: 1px solid rgba(221,221,221,0.3); 47 | } 48 | 49 | .nav-ghbtn { 50 | padding-top: 16px; 51 | } 52 | 53 | a>.text { 54 | display: none; 55 | } 56 | 57 | @media (max-width: 767px) { 58 | .in a>.text, .collapsing a>.text { 59 | display: inline; 60 | font-weight: light; 61 | font-size: 16px; 62 | } 63 | } 64 | 65 | .drag { 66 | -webkit-border-radius: 16px; 67 | -moz-border-radius: 16px; 68 | -ms-border-radius: 16px; 69 | -o-border-radius: 16px; 70 | border-radius: 16px; 71 | -webkit-box-shadow: 0px 10px 16px 4px rgba(128, 128, 128, .5); 72 | -moz-box-shadow: 0px 10px 16px 4px rgba(128, 128, 128, .5); 73 | -ms-box-shadow: 0px 10px 16px 4px rgba(128, 128, 128, .5); 74 | -o-box-shadow: 0px 10px 16px 4px rgba(128, 128, 128, .5); 75 | box-shadow: 0px 10px 16px 4px rgba(128, 128, 128, .5); 76 | background-color: #fff; 77 | padding: .5em; 78 | } 79 | .drag > * { 80 | -webkit-touch-callout: none; 81 | -webkit-user-select: none; 82 | -khtml-user-select: none; 83 | -moz-user-select: none; 84 | -ms-user-select: none; 85 | user-select: none; 86 | } 87 | 88 | .panel > .dropdemo { 89 | min-height: 100px; 90 | } 91 | 92 | .dropzone { 93 | text-align: center; 94 | color: #eee; 95 | } 96 | 97 | .map-body { 98 | height: 435px; 99 | width: 580px; 100 | background: url(../img/map.jpg) no-repeat left top; 101 | margin: 0 auto; 102 | -webkit-border-radius: 16px; 103 | -moz-border-radius: 16px; 104 | -ms-border-radius: 16px; 105 | -o-border-radius: 16px; 106 | border-radius: 16px; 107 | -webkit-box-shadow: 0px 10px 16px 4px rgba(128, 128, 128, .5); 108 | -moz-box-shadow: 0px 10px 16px 4px rgba(128, 128, 128, .5); 109 | -ms-box-shadow: 0px 10px 16px 4px rgba(128, 128, 128, .5); 110 | -o-box-shadow: 0px 10px 16px 4px rgba(128, 128, 128, .5); 111 | box-shadow: 0px 10px 16px 4px rgba(128, 128, 128, .5); 112 | } 113 | 114 | [draggable]:hover { 115 | cursor: move; 116 | } -------------------------------------------------------------------------------- /misc/demo/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:'FontAwesome';src:url('../font/fontawesome-webfont.eot?v=3.2.1');src:url('../font/fontawesome-webfont.eot?#iefix&v=3.2.1') format('embedded-opentype'),url('../font/fontawesome-webfont.woff?v=3.2.1') format('woff'),url('../font/fontawesome-webfont.ttf?v=3.2.1') format('truetype'),url('../font/fontawesome-webfont.svg#fontawesomeregular?v=3.2.1') format('svg');font-weight:normal;font-style:normal;}[class^="icon-"],[class*=" icon-"]{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em;} 2 | [class^="icon-"]:before,[class*=" icon-"]:before{text-decoration:inherit;display:inline-block;speak:none;} 3 | .icon-large:before{vertical-align:-10%;font-size:1.3333333333333333em;} 4 | a [class^="icon-"],a [class*=" icon-"]{display:inline;} 5 | [class^="icon-"].icon-fixed-width,[class*=" icon-"].icon-fixed-width{display:inline-block;width:1.1428571428571428em;text-align:right;padding-right:0.2857142857142857em;}[class^="icon-"].icon-fixed-width.icon-large,[class*=" icon-"].icon-fixed-width.icon-large{width:1.4285714285714286em;} 6 | .icons-ul{margin-left:2.142857142857143em;list-style-type:none;}.icons-ul>li{position:relative;} 7 | .icons-ul .icon-li{position:absolute;left:-2.142857142857143em;width:2.142857142857143em;text-align:center;line-height:inherit;} 8 | [class^="icon-"].hide,[class*=" icon-"].hide{display:none;} 9 | .icon-muted{color:#eeeeee;} 10 | .icon-light{color:#ffffff;} 11 | .icon-dark{color:#333333;} 12 | .icon-border{border:solid 1px #eeeeee;padding:.2em .25em .15em;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} 13 | .icon-2x{font-size:2em;}.icon-2x.icon-border{border-width:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} 14 | .icon-3x{font-size:3em;}.icon-3x.icon-border{border-width:3px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} 15 | .icon-4x{font-size:4em;}.icon-4x.icon-border{border-width:4px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} 16 | .icon-5x{font-size:5em;}.icon-5x.icon-border{border-width:5px;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px;} 17 | .pull-right{float:right;} 18 | .pull-left{float:left;} 19 | [class^="icon-"].pull-left,[class*=" icon-"].pull-left{margin-right:.3em;} 20 | [class^="icon-"].pull-right,[class*=" icon-"].pull-right{margin-left:.3em;} 21 | [class^="icon-"],[class*=" icon-"]{display:inline;width:auto;height:auto;line-height:normal;vertical-align:baseline;background-image:none;background-position:0% 0%;background-repeat:repeat;margin-top:0;} 22 | .icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:none;} 23 | .btn [class^="icon-"].icon-large,.nav [class^="icon-"].icon-large,.btn [class*=" icon-"].icon-large,.nav [class*=" icon-"].icon-large{line-height:.9em;} 24 | .btn [class^="icon-"].icon-spin,.nav [class^="icon-"].icon-spin,.btn [class*=" icon-"].icon-spin,.nav [class*=" icon-"].icon-spin{display:inline-block;} 25 | .nav-tabs [class^="icon-"],.nav-pills [class^="icon-"],.nav-tabs [class*=" icon-"],.nav-pills [class*=" icon-"],.nav-tabs [class^="icon-"].icon-large,.nav-pills [class^="icon-"].icon-large,.nav-tabs [class*=" icon-"].icon-large,.nav-pills [class*=" icon-"].icon-large{line-height:.9em;} 26 | .btn [class^="icon-"].pull-left.icon-2x,.btn [class*=" icon-"].pull-left.icon-2x,.btn [class^="icon-"].pull-right.icon-2x,.btn [class*=" icon-"].pull-right.icon-2x{margin-top:.18em;} 27 | .btn [class^="icon-"].icon-spin.icon-large,.btn [class*=" icon-"].icon-spin.icon-large{line-height:.8em;} 28 | .btn.btn-small [class^="icon-"].pull-left.icon-2x,.btn.btn-small [class*=" icon-"].pull-left.icon-2x,.btn.btn-small [class^="icon-"].pull-right.icon-2x,.btn.btn-small [class*=" icon-"].pull-right.icon-2x{margin-top:.25em;} 29 | .btn.btn-large [class^="icon-"],.btn.btn-large [class*=" icon-"]{margin-top:0;}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x,.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-top:.05em;} 30 | .btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x{margin-right:.2em;} 31 | .btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-left:.2em;} 32 | .nav-list [class^="icon-"],.nav-list [class*=" icon-"]{line-height:inherit;} 33 | .icon-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:-35%;}.icon-stack [class^="icon-"],.icon-stack [class*=" icon-"]{display:block;text-align:center;position:absolute;width:100%;height:100%;font-size:1em;line-height:inherit;*line-height:2em;} 34 | .icon-stack .icon-stack-base{font-size:2em;*line-height:1em;} 35 | .icon-spin{display:inline-block;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear;} 36 | a .icon-stack,a .icon-spin{display:inline-block;text-decoration:none;} 37 | @-moz-keyframes spin{0%{-moz-transform:rotate(0deg);} 100%{-moz-transform:rotate(359deg);}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg);} 100%{-webkit-transform:rotate(359deg);}}@-o-keyframes spin{0%{-o-transform:rotate(0deg);} 100%{-o-transform:rotate(359deg);}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg);} 100%{-ms-transform:rotate(359deg);}}@keyframes spin{0%{transform:rotate(0deg);} 100%{transform:rotate(359deg);}}.icon-rotate-90:before{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);} 38 | .icon-rotate-180:before{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);} 39 | .icon-rotate-270:before{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);} 40 | .icon-flip-horizontal:before{-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1);} 41 | .icon-flip-vertical:before{-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1);} 42 | a .icon-rotate-90:before,a .icon-rotate-180:before,a .icon-rotate-270:before,a .icon-flip-horizontal:before,a .icon-flip-vertical:before{display:inline-block;} 43 | .icon-glass:before{content:"\f000";} 44 | .icon-music:before{content:"\f001";} 45 | .icon-search:before{content:"\f002";} 46 | .icon-envelope-alt:before{content:"\f003";} 47 | .icon-heart:before{content:"\f004";} 48 | .icon-star:before{content:"\f005";} 49 | .icon-star-empty:before{content:"\f006";} 50 | .icon-user:before{content:"\f007";} 51 | .icon-film:before{content:"\f008";} 52 | .icon-th-large:before{content:"\f009";} 53 | .icon-th:before{content:"\f00a";} 54 | .icon-th-list:before{content:"\f00b";} 55 | .icon-ok:before{content:"\f00c";} 56 | .icon-remove:before{content:"\f00d";} 57 | .icon-zoom-in:before{content:"\f00e";} 58 | .icon-zoom-out:before{content:"\f010";} 59 | .icon-power-off:before,.icon-off:before{content:"\f011";} 60 | .icon-signal:before{content:"\f012";} 61 | .icon-gear:before,.icon-cog:before{content:"\f013";} 62 | .icon-trash:before{content:"\f014";} 63 | .icon-home:before{content:"\f015";} 64 | .icon-file-alt:before{content:"\f016";} 65 | .icon-time:before{content:"\f017";} 66 | .icon-road:before{content:"\f018";} 67 | .icon-download-alt:before{content:"\f019";} 68 | .icon-download:before{content:"\f01a";} 69 | .icon-upload:before{content:"\f01b";} 70 | .icon-inbox:before{content:"\f01c";} 71 | .icon-play-circle:before{content:"\f01d";} 72 | .icon-rotate-right:before,.icon-repeat:before{content:"\f01e";} 73 | .icon-refresh:before{content:"\f021";} 74 | .icon-list-alt:before{content:"\f022";} 75 | .icon-lock:before{content:"\f023";} 76 | .icon-flag:before{content:"\f024";} 77 | .icon-headphones:before{content:"\f025";} 78 | .icon-volume-off:before{content:"\f026";} 79 | .icon-volume-down:before{content:"\f027";} 80 | .icon-volume-up:before{content:"\f028";} 81 | .icon-qrcode:before{content:"\f029";} 82 | .icon-barcode:before{content:"\f02a";} 83 | .icon-tag:before{content:"\f02b";} 84 | .icon-tags:before{content:"\f02c";} 85 | .icon-book:before{content:"\f02d";} 86 | .icon-bookmark:before{content:"\f02e";} 87 | .icon-print:before{content:"\f02f";} 88 | .icon-camera:before{content:"\f030";} 89 | .icon-font:before{content:"\f031";} 90 | .icon-bold:before{content:"\f032";} 91 | .icon-italic:before{content:"\f033";} 92 | .icon-text-height:before{content:"\f034";} 93 | .icon-text-width:before{content:"\f035";} 94 | .icon-align-left:before{content:"\f036";} 95 | .icon-align-center:before{content:"\f037";} 96 | .icon-align-right:before{content:"\f038";} 97 | .icon-align-justify:before{content:"\f039";} 98 | .icon-list:before{content:"\f03a";} 99 | .icon-indent-left:before{content:"\f03b";} 100 | .icon-indent-right:before{content:"\f03c";} 101 | .icon-facetime-video:before{content:"\f03d";} 102 | .icon-picture:before{content:"\f03e";} 103 | .icon-pencil:before{content:"\f040";} 104 | .icon-map-marker:before{content:"\f041";} 105 | .icon-adjust:before{content:"\f042";} 106 | .icon-tint:before{content:"\f043";} 107 | .icon-edit:before{content:"\f044";} 108 | .icon-share:before{content:"\f045";} 109 | .icon-check:before{content:"\f046";} 110 | .icon-move:before{content:"\f047";} 111 | .icon-step-backward:before{content:"\f048";} 112 | .icon-fast-backward:before{content:"\f049";} 113 | .icon-backward:before{content:"\f04a";} 114 | .icon-play:before{content:"\f04b";} 115 | .icon-pause:before{content:"\f04c";} 116 | .icon-stop:before{content:"\f04d";} 117 | .icon-forward:before{content:"\f04e";} 118 | .icon-fast-forward:before{content:"\f050";} 119 | .icon-step-forward:before{content:"\f051";} 120 | .icon-eject:before{content:"\f052";} 121 | .icon-chevron-left:before{content:"\f053";} 122 | .icon-chevron-right:before{content:"\f054";} 123 | .icon-plus-sign:before{content:"\f055";} 124 | .icon-minus-sign:before{content:"\f056";} 125 | .icon-remove-sign:before{content:"\f057";} 126 | .icon-ok-sign:before{content:"\f058";} 127 | .icon-question-sign:before{content:"\f059";} 128 | .icon-info-sign:before{content:"\f05a";} 129 | .icon-screenshot:before{content:"\f05b";} 130 | .icon-remove-circle:before{content:"\f05c";} 131 | .icon-ok-circle:before{content:"\f05d";} 132 | .icon-ban-circle:before{content:"\f05e";} 133 | .icon-arrow-left:before{content:"\f060";} 134 | .icon-arrow-right:before{content:"\f061";} 135 | .icon-arrow-up:before{content:"\f062";} 136 | .icon-arrow-down:before{content:"\f063";} 137 | .icon-mail-forward:before,.icon-share-alt:before{content:"\f064";} 138 | .icon-resize-full:before{content:"\f065";} 139 | .icon-resize-small:before{content:"\f066";} 140 | .icon-plus:before{content:"\f067";} 141 | .icon-minus:before{content:"\f068";} 142 | .icon-asterisk:before{content:"\f069";} 143 | .icon-exclamation-sign:before{content:"\f06a";} 144 | .icon-gift:before{content:"\f06b";} 145 | .icon-leaf:before{content:"\f06c";} 146 | .icon-fire:before{content:"\f06d";} 147 | .icon-eye-open:before{content:"\f06e";} 148 | .icon-eye-close:before{content:"\f070";} 149 | .icon-warning-sign:before{content:"\f071";} 150 | .icon-plane:before{content:"\f072";} 151 | .icon-calendar:before{content:"\f073";} 152 | .icon-random:before{content:"\f074";} 153 | .icon-comment:before{content:"\f075";} 154 | .icon-magnet:before{content:"\f076";} 155 | .icon-chevron-up:before{content:"\f077";} 156 | .icon-chevron-down:before{content:"\f078";} 157 | .icon-retweet:before{content:"\f079";} 158 | .icon-shopping-cart:before{content:"\f07a";} 159 | .icon-folder-close:before{content:"\f07b";} 160 | .icon-folder-open:before{content:"\f07c";} 161 | .icon-resize-vertical:before{content:"\f07d";} 162 | .icon-resize-horizontal:before{content:"\f07e";} 163 | .icon-bar-chart:before{content:"\f080";} 164 | .icon-twitter-sign:before{content:"\f081";} 165 | .icon-facebook-sign:before{content:"\f082";} 166 | .icon-camera-retro:before{content:"\f083";} 167 | .icon-key:before{content:"\f084";} 168 | .icon-gears:before,.icon-cogs:before{content:"\f085";} 169 | .icon-comments:before{content:"\f086";} 170 | .icon-thumbs-up-alt:before{content:"\f087";} 171 | .icon-thumbs-down-alt:before{content:"\f088";} 172 | .icon-star-half:before{content:"\f089";} 173 | .icon-heart-empty:before{content:"\f08a";} 174 | .icon-signout:before{content:"\f08b";} 175 | .icon-linkedin-sign:before{content:"\f08c";} 176 | .icon-pushpin:before{content:"\f08d";} 177 | .icon-external-link:before{content:"\f08e";} 178 | .icon-signin:before{content:"\f090";} 179 | .icon-trophy:before{content:"\f091";} 180 | .icon-github-sign:before{content:"\f092";} 181 | .icon-upload-alt:before{content:"\f093";} 182 | .icon-lemon:before{content:"\f094";} 183 | .icon-phone:before{content:"\f095";} 184 | .icon-unchecked:before,.icon-check-empty:before{content:"\f096";} 185 | .icon-bookmark-empty:before{content:"\f097";} 186 | .icon-phone-sign:before{content:"\f098";} 187 | .icon-twitter:before{content:"\f099";} 188 | .icon-facebook:before{content:"\f09a";} 189 | .icon-github:before{content:"\f09b";} 190 | .icon-unlock:before{content:"\f09c";} 191 | .icon-credit-card:before{content:"\f09d";} 192 | .icon-rss:before{content:"\f09e";} 193 | .icon-hdd:before{content:"\f0a0";} 194 | .icon-bullhorn:before{content:"\f0a1";} 195 | .icon-bell:before{content:"\f0a2";} 196 | .icon-certificate:before{content:"\f0a3";} 197 | .icon-hand-right:before{content:"\f0a4";} 198 | .icon-hand-left:before{content:"\f0a5";} 199 | .icon-hand-up:before{content:"\f0a6";} 200 | .icon-hand-down:before{content:"\f0a7";} 201 | .icon-circle-arrow-left:before{content:"\f0a8";} 202 | .icon-circle-arrow-right:before{content:"\f0a9";} 203 | .icon-circle-arrow-up:before{content:"\f0aa";} 204 | .icon-circle-arrow-down:before{content:"\f0ab";} 205 | .icon-globe:before{content:"\f0ac";} 206 | .icon-wrench:before{content:"\f0ad";} 207 | .icon-tasks:before{content:"\f0ae";} 208 | .icon-filter:before{content:"\f0b0";} 209 | .icon-briefcase:before{content:"\f0b1";} 210 | .icon-fullscreen:before{content:"\f0b2";} 211 | .icon-group:before{content:"\f0c0";} 212 | .icon-link:before{content:"\f0c1";} 213 | .icon-cloud:before{content:"\f0c2";} 214 | .icon-beaker:before{content:"\f0c3";} 215 | .icon-cut:before{content:"\f0c4";} 216 | .icon-copy:before{content:"\f0c5";} 217 | .icon-paperclip:before,.icon-paper-clip:before{content:"\f0c6";} 218 | .icon-save:before{content:"\f0c7";} 219 | .icon-sign-blank:before{content:"\f0c8";} 220 | .icon-reorder:before{content:"\f0c9";} 221 | .icon-list-ul:before{content:"\f0ca";} 222 | .icon-list-ol:before{content:"\f0cb";} 223 | .icon-strikethrough:before{content:"\f0cc";} 224 | .icon-underline:before{content:"\f0cd";} 225 | .icon-table:before{content:"\f0ce";} 226 | .icon-magic:before{content:"\f0d0";} 227 | .icon-truck:before{content:"\f0d1";} 228 | .icon-pinterest:before{content:"\f0d2";} 229 | .icon-pinterest-sign:before{content:"\f0d3";} 230 | .icon-google-plus-sign:before{content:"\f0d4";} 231 | .icon-google-plus:before{content:"\f0d5";} 232 | .icon-money:before{content:"\f0d6";} 233 | .icon-caret-down:before{content:"\f0d7";} 234 | .icon-caret-up:before{content:"\f0d8";} 235 | .icon-caret-left:before{content:"\f0d9";} 236 | .icon-caret-right:before{content:"\f0da";} 237 | .icon-columns:before{content:"\f0db";} 238 | .icon-sort:before{content:"\f0dc";} 239 | .icon-sort-down:before{content:"\f0dd";} 240 | .icon-sort-up:before{content:"\f0de";} 241 | .icon-envelope:before{content:"\f0e0";} 242 | .icon-linkedin:before{content:"\f0e1";} 243 | .icon-rotate-left:before,.icon-undo:before{content:"\f0e2";} 244 | .icon-legal:before{content:"\f0e3";} 245 | .icon-dashboard:before{content:"\f0e4";} 246 | .icon-comment-alt:before{content:"\f0e5";} 247 | .icon-comments-alt:before{content:"\f0e6";} 248 | .icon-bolt:before{content:"\f0e7";} 249 | .icon-sitemap:before{content:"\f0e8";} 250 | .icon-umbrella:before{content:"\f0e9";} 251 | .icon-paste:before{content:"\f0ea";} 252 | .icon-lightbulb:before{content:"\f0eb";} 253 | .icon-exchange:before{content:"\f0ec";} 254 | .icon-cloud-download:before{content:"\f0ed";} 255 | .icon-cloud-upload:before{content:"\f0ee";} 256 | .icon-user-md:before{content:"\f0f0";} 257 | .icon-stethoscope:before{content:"\f0f1";} 258 | .icon-suitcase:before{content:"\f0f2";} 259 | .icon-bell-alt:before{content:"\f0f3";} 260 | .icon-coffee:before{content:"\f0f4";} 261 | .icon-food:before{content:"\f0f5";} 262 | .icon-file-text-alt:before{content:"\f0f6";} 263 | .icon-building:before{content:"\f0f7";} 264 | .icon-hospital:before{content:"\f0f8";} 265 | .icon-ambulance:before{content:"\f0f9";} 266 | .icon-medkit:before{content:"\f0fa";} 267 | .icon-fighter-jet:before{content:"\f0fb";} 268 | .icon-beer:before{content:"\f0fc";} 269 | .icon-h-sign:before{content:"\f0fd";} 270 | .icon-plus-sign-alt:before{content:"\f0fe";} 271 | .icon-double-angle-left:before{content:"\f100";} 272 | .icon-double-angle-right:before{content:"\f101";} 273 | .icon-double-angle-up:before{content:"\f102";} 274 | .icon-double-angle-down:before{content:"\f103";} 275 | .icon-angle-left:before{content:"\f104";} 276 | .icon-angle-right:before{content:"\f105";} 277 | .icon-angle-up:before{content:"\f106";} 278 | .icon-angle-down:before{content:"\f107";} 279 | .icon-desktop:before{content:"\f108";} 280 | .icon-laptop:before{content:"\f109";} 281 | .icon-tablet:before{content:"\f10a";} 282 | .icon-mobile-phone:before{content:"\f10b";} 283 | .icon-circle-blank:before{content:"\f10c";} 284 | .icon-quote-left:before{content:"\f10d";} 285 | .icon-quote-right:before{content:"\f10e";} 286 | .icon-spinner:before{content:"\f110";} 287 | .icon-circle:before{content:"\f111";} 288 | .icon-mail-reply:before,.icon-reply:before{content:"\f112";} 289 | .icon-github-alt:before{content:"\f113";} 290 | .icon-folder-close-alt:before{content:"\f114";} 291 | .icon-folder-open-alt:before{content:"\f115";} 292 | .icon-expand-alt:before{content:"\f116";} 293 | .icon-collapse-alt:before{content:"\f117";} 294 | .icon-smile:before{content:"\f118";} 295 | .icon-frown:before{content:"\f119";} 296 | .icon-meh:before{content:"\f11a";} 297 | .icon-gamepad:before{content:"\f11b";} 298 | .icon-keyboard:before{content:"\f11c";} 299 | .icon-flag-alt:before{content:"\f11d";} 300 | .icon-flag-checkered:before{content:"\f11e";} 301 | .icon-terminal:before{content:"\f120";} 302 | .icon-code:before{content:"\f121";} 303 | .icon-reply-all:before{content:"\f122";} 304 | .icon-mail-reply-all:before{content:"\f122";} 305 | .icon-star-half-full:before,.icon-star-half-empty:before{content:"\f123";} 306 | .icon-location-arrow:before{content:"\f124";} 307 | .icon-crop:before{content:"\f125";} 308 | .icon-code-fork:before{content:"\f126";} 309 | .icon-unlink:before{content:"\f127";} 310 | .icon-question:before{content:"\f128";} 311 | .icon-info:before{content:"\f129";} 312 | .icon-exclamation:before{content:"\f12a";} 313 | .icon-superscript:before{content:"\f12b";} 314 | .icon-subscript:before{content:"\f12c";} 315 | .icon-eraser:before{content:"\f12d";} 316 | .icon-puzzle-piece:before{content:"\f12e";} 317 | .icon-microphone:before{content:"\f130";} 318 | .icon-microphone-off:before{content:"\f131";} 319 | .icon-shield:before{content:"\f132";} 320 | .icon-calendar-empty:before{content:"\f133";} 321 | .icon-fire-extinguisher:before{content:"\f134";} 322 | .icon-rocket:before{content:"\f135";} 323 | .icon-maxcdn:before{content:"\f136";} 324 | .icon-chevron-sign-left:before{content:"\f137";} 325 | .icon-chevron-sign-right:before{content:"\f138";} 326 | .icon-chevron-sign-up:before{content:"\f139";} 327 | .icon-chevron-sign-down:before{content:"\f13a";} 328 | .icon-html5:before{content:"\f13b";} 329 | .icon-css3:before{content:"\f13c";} 330 | .icon-anchor:before{content:"\f13d";} 331 | .icon-unlock-alt:before{content:"\f13e";} 332 | .icon-bullseye:before{content:"\f140";} 333 | .icon-ellipsis-horizontal:before{content:"\f141";} 334 | .icon-ellipsis-vertical:before{content:"\f142";} 335 | .icon-rss-sign:before{content:"\f143";} 336 | .icon-play-sign:before{content:"\f144";} 337 | .icon-ticket:before{content:"\f145";} 338 | .icon-minus-sign-alt:before{content:"\f146";} 339 | .icon-check-minus:before{content:"\f147";} 340 | .icon-level-up:before{content:"\f148";} 341 | .icon-level-down:before{content:"\f149";} 342 | .icon-check-sign:before{content:"\f14a";} 343 | .icon-edit-sign:before{content:"\f14b";} 344 | .icon-external-link-sign:before{content:"\f14c";} 345 | .icon-share-sign:before{content:"\f14d";} 346 | .icon-compass:before{content:"\f14e";} 347 | .icon-collapse:before{content:"\f150";} 348 | .icon-collapse-top:before{content:"\f151";} 349 | .icon-expand:before{content:"\f152";} 350 | .icon-euro:before,.icon-eur:before{content:"\f153";} 351 | .icon-gbp:before{content:"\f154";} 352 | .icon-dollar:before,.icon-usd:before{content:"\f155";} 353 | .icon-rupee:before,.icon-inr:before{content:"\f156";} 354 | .icon-yen:before,.icon-jpy:before{content:"\f157";} 355 | .icon-renminbi:before,.icon-cny:before{content:"\f158";} 356 | .icon-won:before,.icon-krw:before{content:"\f159";} 357 | .icon-bitcoin:before,.icon-btc:before{content:"\f15a";} 358 | .icon-file:before{content:"\f15b";} 359 | .icon-file-text:before{content:"\f15c";} 360 | .icon-sort-by-alphabet:before{content:"\f15d";} 361 | .icon-sort-by-alphabet-alt:before{content:"\f15e";} 362 | .icon-sort-by-attributes:before{content:"\f160";} 363 | .icon-sort-by-attributes-alt:before{content:"\f161";} 364 | .icon-sort-by-order:before{content:"\f162";} 365 | .icon-sort-by-order-alt:before{content:"\f163";} 366 | .icon-thumbs-up:before{content:"\f164";} 367 | .icon-thumbs-down:before{content:"\f165";} 368 | .icon-youtube-sign:before{content:"\f166";} 369 | .icon-youtube:before{content:"\f167";} 370 | .icon-xing:before{content:"\f168";} 371 | .icon-xing-sign:before{content:"\f169";} 372 | .icon-youtube-play:before{content:"\f16a";} 373 | .icon-dropbox:before{content:"\f16b";} 374 | .icon-stackexchange:before{content:"\f16c";} 375 | .icon-instagram:before{content:"\f16d";} 376 | .icon-flickr:before{content:"\f16e";} 377 | .icon-adn:before{content:"\f170";} 378 | .icon-bitbucket:before{content:"\f171";} 379 | .icon-bitbucket-sign:before{content:"\f172";} 380 | .icon-tumblr:before{content:"\f173";} 381 | .icon-tumblr-sign:before{content:"\f174";} 382 | .icon-long-arrow-down:before{content:"\f175";} 383 | .icon-long-arrow-up:before{content:"\f176";} 384 | .icon-long-arrow-left:before{content:"\f177";} 385 | .icon-long-arrow-right:before{content:"\f178";} 386 | .icon-apple:before{content:"\f179";} 387 | .icon-windows:before{content:"\f17a";} 388 | .icon-android:before{content:"\f17b";} 389 | .icon-linux:before{content:"\f17c";} 390 | .icon-dribbble:before{content:"\f17d";} 391 | .icon-skype:before{content:"\f17e";} 392 | .icon-foursquare:before{content:"\f180";} 393 | .icon-trello:before{content:"\f181";} 394 | .icon-female:before{content:"\f182";} 395 | .icon-male:before{content:"\f183";} 396 | .icon-gittip:before{content:"\f184";} 397 | .icon-sun:before{content:"\f185";} 398 | .icon-moon:before{content:"\f186";} 399 | .icon-archive:before{content:"\f187";} 400 | .icon-bug:before{content:"\f188";} 401 | .icon-vk:before{content:"\f189";} 402 | .icon-weibo:before{content:"\f18a";} 403 | .icon-renren:before{content:"\f18b";} -------------------------------------------------------------------------------- /misc/demo/font/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caitp/angular-drop/be9c9569b71aeb00043a4c51931a00d5783c2bb2/misc/demo/font/fontawesome-webfont.eot -------------------------------------------------------------------------------- /misc/demo/font/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caitp/angular-drop/be9c9569b71aeb00043a4c51931a00d5783c2bb2/misc/demo/font/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /misc/demo/font/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caitp/angular-drop/be9c9569b71aeb00043a4c51931a00d5783c2bb2/misc/demo/font/fontawesome-webfont.woff -------------------------------------------------------------------------------- /misc/demo/img/appbar.location.round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caitp/angular-drop/be9c9569b71aeb00043a4c51931a00d5783c2bb2/misc/demo/img/appbar.location.round.png -------------------------------------------------------------------------------- /misc/demo/img/map.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caitp/angular-drop/be9c9569b71aeb00043a4c51931a00d5783c2bb2/misc/demo/img/map.jpg -------------------------------------------------------------------------------- /misc/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AngularDrop 5 | 6 | 7 | 8 | 9 | 10 |
11 | 41 |
42 |
43 |

AngularDrop

44 | 45 |

Drag & Drop functionality in AngularJS, no jQuery required

46 |

Simple and easy to use tools for building drag & drop directives, or integrating drag & drop functionality in your own directives, with a very simple and easy to use API.

47 |

48 | 49 | Code on Github 50 | 52 | Download (<%= pkg.version%>) 53 | API Documentation 54 |

55 |
56 |
57 |
58 |
59 | 62 |
63 |
65 |
66 |

Drag me anywhere you want me to go

67 |

If dropped into one of these three areas, I will snap into place. Otherwise, I'll just hang around.

68 |
69 |

[ Drop Zone ]

70 |
71 |
72 |

[ Drop Zone ]

73 |
74 | 77 | 80 |
81 | 84 |
85 |
86 |

Drag the cursor around the world

87 |
88 |
89 |
90 | Drag me around 91 |
92 |
93 | 95 |
96 |
97 | 98 | 99 | 100 | 101 | 102 | 103 | 113 | 114 | -------------------------------------------------------------------------------- /misc/demo/js/app.js: -------------------------------------------------------------------------------- 1 | angular.module("dropDemo", ["ui.drop"]) 2 | 3 | .controller("DemoCtrl", function($scope) { 4 | 5 | }) 6 | .controller("DropCtrl", function($scope, $element) { 7 | $scope.hasElement = function() { 8 | return $element.children('div').length > 0; 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /misc/demo/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * bootstrap.js v3.0.0 by @fat and @mdo 3 | * Copyright 2013 Twitter Inc. 4 | * http://www.apache.org/licenses/LICENSE-2.0 5 | */ 6 | if(!jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]}}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(window.jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d)};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.is("input")?"val":"html",e=c.data();a+="Text",e.resetText||c.data("resetText",c[d]()),c[d](e[a]||this.options[a]),setTimeout(function(){"loadingText"==a?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons"]');if(a.length){var b=this.$element.find("input").prop("checked",!this.$element.hasClass("active")).trigger("change");"radio"===b.prop("type")&&a.find(".active").removeClass("active")}this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(window.jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}this.sliding=!0,f&&this.pause();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(window.jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(window.jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(window.jQuery); -------------------------------------------------------------------------------- /misc/validate-commit-msg.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Git COMMIT-MSG hook for validating commit message 5 | * See https://docs.google.com/document/d/1rk04jEuGfk9kYzfqCuOlPTSJw3hEDZJTBN5E5f1SALo/edit 6 | * 7 | * Installation: 8 | * >> cd 9 | * >> ln -s validate-commit-msg.js .git/hooks/commit-msg 10 | */ 11 | var fs = require('fs'); 12 | var util = require('util'); 13 | 14 | 15 | var MAX_LENGTH = 70; 16 | var PATTERN = /^(?:fixup!\s*)?(\w*)(\(((?:\w|[$,.-])+)\))?\: (.*)$/; 17 | var IGNORED = /^WIP\:/; 18 | var TYPES = { 19 | chore: true, 20 | demo: true, 21 | docs: true, 22 | feat: true, 23 | fix: true, 24 | refactor: true, 25 | revert: true, 26 | style: true, 27 | test: true 28 | }; 29 | 30 | 31 | var error = function() { 32 | // gitx does not display it 33 | // http://gitx.lighthouseapp.com/projects/17830/tickets/294-feature-display-hook-error-message-when-hook-fails 34 | // https://groups.google.com/group/gitx/browse_thread/thread/a03bcab60844b812 35 | console.error('INVALID COMMIT MSG: ' + util.format.apply(null, arguments)); 36 | }; 37 | 38 | 39 | var validateMessage = function(message) { 40 | var isValid = true; 41 | 42 | if (IGNORED.test(message)) { 43 | console.log('Commit message validation ignored.'); 44 | return true; 45 | } 46 | 47 | if (message.length > MAX_LENGTH) { 48 | error('is longer than %d characters !', MAX_LENGTH); 49 | isValid = false; 50 | } 51 | 52 | var match = PATTERN.exec(message); 53 | 54 | if (!match) { 55 | error('does not match "(): " ! was: "' + message + '"\nNote: must be only letters.'); 56 | return false; 57 | } 58 | 59 | var type = match[1]; 60 | var scope = match[3]; 61 | var subject = match[4]; 62 | 63 | if (!TYPES.hasOwnProperty(type)) { 64 | error('"%s" is not allowed type !', type); 65 | return false; 66 | } 67 | 68 | // Some more ideas, do want anything like this ? 69 | // - allow only specific scopes (eg. fix(docs) should not be allowed ? 70 | // - auto correct the type to lower case ? 71 | // - auto correct first letter of the subject to lower case ? 72 | // - auto add empty line after subject ? 73 | // - auto remove empty () ? 74 | // - auto correct typos in type ? 75 | // - store incorrect messages, so that we can learn 76 | 77 | return isValid; 78 | }; 79 | 80 | 81 | var firstLineFromBuffer = function(buffer) { 82 | return buffer.toString().split('\n').shift(); 83 | }; 84 | 85 | 86 | 87 | // publish for testing 88 | exports.validateMessage = validateMessage; 89 | 90 | // hacky start if not run by jasmine :-D 91 | if (process.argv.join('').indexOf('jasmine-node') === -1) { 92 | var commitMsgFile = process.argv[2]; 93 | var incorrectLogFile = commitMsgFile.replace('COMMIT_EDITMSG', 'logs/incorrect-commit-msgs'); 94 | 95 | fs.readFile(commitMsgFile, function(err, buffer) { 96 | var msg = firstLineFromBuffer(buffer); 97 | 98 | if (!validateMessage(msg)) { 99 | fs.appendFile(incorrectLogFile, msg + '\n', function() { 100 | process.exit(1); 101 | }); 102 | } else { 103 | process.exit(0); 104 | } 105 | }); 106 | } 107 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-drop", 3 | "version": "0.0.1-snapshot", 4 | "cdnVersion": "0.0.1", 5 | "codename": "badger", 6 | "description": "Drag & Drop functionality in AngularJS, no jQuery required", 7 | "author": "Caitlin Potter ", 8 | "license": "MIT", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/caitp/angular-drop.git" 12 | }, 13 | "devDependencies": { 14 | "karma": "~0.12.0", 15 | "grunt": "~0.4.1", 16 | "grunt-ngdocs-caitp": "~0.1.9", 17 | "grunt-conventional-changelog": "~1.2.1", 18 | "grunt-contrib-concat": "~0.5.0", 19 | "grunt-contrib-copy": "~0.8.0", 20 | "grunt-contrib-uglify": "~0.9.1", 21 | "grunt-contrib-watch": "~0.6.1", 22 | "grunt-contrib-jshint": "~0.11.0", 23 | "grunt-html2js": "~0.3.0", 24 | "grunt-karma": "~0.11.0", 25 | "grunt-ngmin": "0.0.3", 26 | "semver": "~4.3.3", 27 | "shelljs": "~0.5.0", 28 | "grunt-gh-pages": "~0.10.0", 29 | "grunt-contrib-connect": "~0.10.1", 30 | "grunt-contrib-clean": "~0.6.0", 31 | "grunt-merge-conflict": "0.0.2", 32 | "grunt-ddescribe-iit": "0.0.6", 33 | "grunt-shell": "~1.1.1", 34 | "grunt-parallel": "~0.4.0", 35 | "grunt-contrib-compress": "~0.13.0", 36 | "bower": "~1.4.1", 37 | "karma-jasmine": "~0.3.4", 38 | "karma-firefox-launcher": "~0.1.3", 39 | "karma-phantomjs-launcher": "~0.2.0", 40 | "karma-coverage": "^0.4.2", 41 | "grunt-bower-release": "0.0.5" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /scripts/travis/init_logs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | rm -rf logs 4 | mkdir logs 5 | touch logs/karma-jqlite.log 6 | touch logs/karma-jquery.log 7 | -------------------------------------------------------------------------------- /src/dndDOM.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc object 5 | * @module ui.drop 6 | * @name ui.drop.$dndDOM 7 | * @requires $document 8 | * @requires $window 9 | * 10 | * @description 11 | * 12 | * A set of utility methods that can be use to retrieve and manipulate DOM properties. 13 | * 14 | * (based on https://github.com/angular-ui/bootstrap/blob/master/src/position/position.js) 15 | * 16 | */ 17 | var $dndDOMFactory = ['$document', '$window', function ($document, $window) { 18 | 19 | function getStyle(el, cssprop) { 20 | if (el.currentStyle) { //IE 21 | return el.currentStyle[cssprop]; 22 | } else if ($window.getComputedStyle) { 23 | return $window.getComputedStyle(el)[cssprop]; 24 | } 25 | // finally try and get inline style 26 | return el.style[cssprop]; 27 | } 28 | 29 | /** 30 | * Checks if a given element is statically positioned 31 | * @param element - raw DOM element 32 | */ 33 | function isStaticPositioned(element) { 34 | return (getStyle(element, 'position') || 'static' ) === 'static'; 35 | } 36 | 37 | /** 38 | * returns the closest, non-statically positioned parentOffset of a given element 39 | * @param element 40 | */ 41 | var parentOffsetEl = function (element) { 42 | var docDomEl = $document[0]; 43 | var offsetParent = element.offsetParent || docDomEl; 44 | while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) { 45 | offsetParent = offsetParent.offsetParent; 46 | } 47 | return offsetParent || docDomEl; 48 | }; 49 | 50 | function contains(a, b) { 51 | var adown = a.nodeType === 9 ? a.documentElement : a, 52 | bup = b && b.parentNode; 53 | return a === bup || !!( bup && bup.nodeType === 1 && contains(adown, bup) ); 54 | }; 55 | 56 | function swapCss(element, css, callback, args) { 57 | var ret, prop, old = {}; 58 | for (prop in css) { 59 | old[prop] = element.style[prop]; 60 | element.style[prop] = css[prop]; 61 | } 62 | 63 | ret = callback.apply(element, args || []); 64 | 65 | for (prop in css) { 66 | element.style[prop] = old[prop]; 67 | } 68 | 69 | return ret; 70 | }; 71 | 72 | var swapDisplay = /^(none|table(?!-c[ea]).+)/; 73 | 74 | var cssShow = { 75 | position: 'absolute', 76 | visibility: 'hidden', 77 | display: 'block' 78 | }; 79 | 80 | return { 81 | /** 82 | * Provides read-only equivalent of jQuery's position function: 83 | * http://api.jquery.com/position/ 84 | */ 85 | position: function (element) { 86 | var elBCR = this.offset(element); 87 | var offsetParentBCR = { top: 0, left: 0 }; 88 | var offsetParentEl = parentOffsetEl(element[0]); 89 | if (offsetParentEl != $document[0]) { 90 | offsetParentBCR = this.offset(angular.element(offsetParentEl)); 91 | offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; 92 | offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; 93 | } 94 | 95 | var boundingClientRect = element[0].getBoundingClientRect(); 96 | return { 97 | width: boundingClientRect.width || element.prop('offsetWidth'), 98 | height: boundingClientRect.height || element.prop('offsetHeight'), 99 | top: elBCR.top - offsetParentBCR.top, 100 | left: elBCR.left - offsetParentBCR.left 101 | }; 102 | }, 103 | 104 | /** 105 | * Provides read-only equivalent of jQuery's offset function: 106 | * http://api.jquery.com/offset/ 107 | */ 108 | offset: function (element) { 109 | 110 | var doc = element[0] && element[0].ownerDocument; 111 | 112 | if (!doc) { 113 | return; 114 | } 115 | 116 | doc = doc.documentElement; 117 | 118 | if (!contains(doc, element[0])) { 119 | return { top: 0, left: 0 }; 120 | } 121 | 122 | var boundingClientRect = element[0].getBoundingClientRect(); 123 | return { 124 | width: boundingClientRect.width || element.prop('offsetWidth'), 125 | height: boundingClientRect.height || element.prop('offsetHeight'), 126 | top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop), 127 | left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft) 128 | }; 129 | }, 130 | 131 | /** 132 | * Partial implementation of https://api.jquery.com/closest/ 133 | * @param element 134 | * @param value 135 | * @returns {angular.element} 136 | */ 137 | closest: function(element, value) { 138 | element = angular.element(element); 139 | if ($.fn && angular.isFunction($.fn.closest)) { 140 | return element.closest(value); 141 | } 142 | // Otherwise, assume it's a tag name... 143 | element = element[0]; 144 | value = value.toLowerCase(); 145 | do { 146 | if (element.nodeName.toLowerCase() === value) { 147 | return angular.element(element); 148 | } 149 | } while (element = element.parentNode); 150 | }, 151 | 152 | size: function(element) { 153 | var jq = angular.element(element); 154 | element = element.nodeName ? element : element[0]; 155 | if (element.offsetWidth === 0 && swapDisplay.test(jq.css('display'))) { 156 | return swapCss(element, cssShow, getHeightAndWidth, [element]); 157 | } 158 | return getHeightAndWidth(element); 159 | 160 | function getHeightAndWidth(element) { 161 | return { 162 | width: element.offsetWidth, 163 | height: element.offsetHeight 164 | }; 165 | } 166 | }, 167 | 168 | keepSize: function(element) { 169 | var css = this.size(element); 170 | css.width = css.width + 'px'; 171 | css.height = css.height + 'px'; 172 | return css; 173 | } 174 | }; 175 | }]; 176 | -------------------------------------------------------------------------------- /src/draggable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc directive 5 | * @module ui.drop 6 | * @name ui.drop.directive:draggable 7 | * 8 | * @description 9 | * 10 | * Simple directive which denotes a 'draggable' widget. Currently, there are no parameters, 11 | * and it is impossible to configure the directive's behaviour. 12 | * 13 | * @param {boolean=} drag-keep-size The dragged item should maintain its original size while dragging 14 | * if set to true. Default: false. 15 | * @param {string=} drag-within Provides a constraint to limit the area through which an element may 16 | * be dragged. It may be a tag name, or the special value '^' or 'parent' to refer to the parent 17 | * element. If jQuery is in use, it may also be a selector supported by jQuery's 18 | * {@link http://api.jquery.com/closest/ $.fn.closest}. 19 | */ 20 | var draggableOptions = { 21 | 'dragKeepSize': 'keepSize', 22 | 'dragWithin': 'dragWithin' 23 | }; 24 | var draggableDirective = [ 25 | '$drag', '$document', '$interpolate', '$timeout', function($drag, $document, $interpolate, $timeout) { 26 | return { 27 | restrict: 'A', 28 | link: function(scope, element, attrs) { 29 | var options = {}; 30 | angular.forEach(draggableOptions, function(name, attr) { 31 | if (typeof attrs[attr] !== 'undefined') { 32 | options[name] = unconst($interpolate(attrs[attr], false)(scope)); 33 | } 34 | }); 35 | $drag.draggable(element, options); 36 | } 37 | }; 38 | }]; 39 | 40 | /** 41 | * @ngdoc object 42 | * @module ui.drop 43 | * @name ui.drop.$dragProvider 44 | * 45 | * @description 46 | * 47 | * TODO: enable the configuration of default Draggable options in $dragProvider. 48 | */ 49 | var $dragProvider = function() { 50 | /** 51 | * @ngdoc object 52 | * @module ui.drop 53 | * @name ui.drop.$drag 54 | * @requires $document 55 | * @requires $drop 56 | * 57 | * @description 58 | * 59 | * Service responsible for controlling the behaviour of draggable nodes. $drag provides 60 | * a mechanism to drag-enable any arbitrary element, which allows it to be used in 61 | * custom directives, so that custom dragging behaviour can be achieved. 62 | */ 63 | this.$get = ['$document', '$drop', '$dndDOM', function($document, $drop, $dndDOM) { 64 | var $drag = { 65 | /** 66 | * @ngdoc method 67 | * @module ui.drop 68 | * @name ui.drop.$drag#isDraggable 69 | * @methodOf ui.drop.$drag 70 | * @returns {boolean} The draggable status of an element (true if an element is 71 | * drag-enabled, otherwise false) 72 | * 73 | * @description 74 | * 75 | * Query the drag-enabled status of an element. Drag-enabled in this context means 76 | * that the element has Draggable state attached to it, and does not currently 77 | * include other factors which might enable or disable the dragging of an element. 78 | */ 79 | isDraggable: function(element) { 80 | return !!$drag.draggable(element, false); 81 | }, 82 | 83 | 84 | /** 85 | * @ngdoc method 86 | * @module ui.drop 87 | * @name ui.drop.$drag#draggable 88 | * @methodOf ui.drop.$drag 89 | * @returns {ui.drop.$drag.Draggable} The Draggable state bound to the element. 90 | * 91 | * @param {element} element jQuery / jqLite element or DOM node to be checked for 92 | * Draggable state, or to be the element to which a new Draggable state is associated. 93 | * @param {object|boolean} options Configuration options for the Draggable state to be 94 | * created. If set to false, no Draggable state will be created, and the function 95 | * instead acts as a simple query. Option values include: 96 | * 97 | * - keepSize {boolean} The dragged item should maintain its original size while dragging 98 | * if set to true. Default: false. 99 | * - dragWithin {string} Provides a constraint to limit the area through which an element 100 | * may be dragged. It may be a tag name, or the special value '^' or 'parent' to refer to 101 | * the parent element. If jQuery is in use, it may also be a selector supported by 102 | * jQuery's {@link http://api.jquery.com/closest/ $.fn.closest}. 103 | * 104 | * @description 105 | * 106 | * Queries for the Draggable state of a DOM node. If the element is not 107 | * Draggable, and `options !== false`, then a new Draggable object is instantiated 108 | * and associated with the element. 109 | * 110 | * As such, this method can be used to query for the existence of Draggable state 111 | * attached to a DOM node, similar to {@link ui.drop.$drag#isDraggable isDraggable()}. 112 | */ 113 | draggable: function(element, options) { 114 | element = angular.element(element); 115 | 116 | var $draggable = element.data("$draggable"); 117 | 118 | if ($draggable) { 119 | return $draggable; 120 | } 121 | 122 | if (options === false) { 123 | return undefined; 124 | } 125 | 126 | options = angular.extend({ 127 | delay: 500, 128 | distance: 1 129 | }, options || {}); 130 | 131 | $draggable = new Draggable(); 132 | 133 | $draggable.element = element; 134 | $draggable.options = options; 135 | 136 | element.on("mousedown", $draggable.dragStart); 137 | 138 | element.data("$draggable", $draggable); 139 | return $draggable; 140 | } 141 | }; 142 | 143 | 144 | /** 145 | * @ngdoc object 146 | * @module ui.drop 147 | * @name ui.drop.$drag.Draggable 148 | * 149 | * @description 150 | * Draggable state is an object containing the drag state of a particular DOM node. 151 | * It is instantiated and attached via {@link ui.drop.$drag#draggable draggable()}. 152 | * 153 | * The Draggable object is attached to a DOM node via jqLite / jQuery's expandostore, 154 | * using the key `$draggable`. However, it is recomended that querying for a node's 155 | * Draggable state be done using {@link ui.drop.$drag#draggable draggable()} or 156 | * {@link ui.drop.$drag#isDraggable isDraggable()}, in case the expandostore key is 157 | * changed in the future. 158 | * 159 | * This object provides several helpers which may be used in custom draggable node 160 | * directives in order to customize the behaviour of drag & drop. 161 | */ 162 | Draggable.prototype = { 163 | constructor: Draggable, 164 | 165 | /** 166 | * @ngdoc function 167 | * @module ui.drop 168 | * @name ui.drop.$drag.Draggable#dragStart 169 | * @methodOf ui.drop.$drag.Draggable 170 | * @function 171 | * 172 | * @param {MouseEvent} event The event to which dragStart is responding 173 | * 174 | * @description 175 | * dragStart is meant to be called in response to a mouse event such as mousedown. 176 | * This routine is bound to the element's mousedown event during the construction of 177 | * the Draggable state. 178 | * 179 | * If it is desirable to simulate an event, the only properties which are actually 180 | * used in the event are Event.pageX and Event.pageY, which respectfully represent the 181 | * position relative to the left edge of the document, and the position relative to the 182 | * top edge of the document. 183 | * 184 | * See {@link https://developer.mozilla.org/en-US/docs/Web/API/event.pageX event.pageX} 185 | * and {@link https://developer.mozilla.org/en-US/docs/Web/API/event.pageY event.pageY} 186 | * for more details. 187 | */ 188 | dragStart: function(event) { 189 | event = fixup(event); 190 | event.preventDefault(); 191 | var self = isDraggable(this) ? this : $drag.draggable(this); 192 | 193 | if (currentDrag) { 194 | currentDrag.dragEnd(event); 195 | } 196 | 197 | currentDrag = self; 198 | 199 | self.cssDisplay = self.element.css('display'); 200 | self.dimensions = $dndDOM.size(self.element); 201 | if (!self.hanging) { 202 | self.cssPosition = self.element.css("position"); 203 | 204 | if (self.options.keepSize) { 205 | self.keepSize = { 206 | width: self.element[0].style['width'], 207 | height: self.element[0].style['height'] 208 | }; 209 | self.element.css($dndDOM.keepSize(self.element)); 210 | } 211 | } 212 | 213 | if (typeof self.options.dragWithin === 'string') { 214 | self.constraints = dragConstraints(self.options.dragWithin, self.element); 215 | } 216 | 217 | self.offset = $dndDOM.position(self.element); 218 | 219 | self.offset.scroll = false; 220 | 221 | angular.extend(self.offset, { 222 | click: { 223 | top: event.pageY - self.offset.top, 224 | left: event.pageX - self.offset.left 225 | } 226 | }); 227 | 228 | self.lastMouseY = event.pageY; 229 | self.lastMouseX = event.pageX; 230 | 231 | self.startEvent = event; 232 | 233 | self.element.css({ 234 | position: 'absolute', 235 | left: self.offset.left + 'px', //jqlite does not support raw Number 236 | top: self.offset.top + 'px' 237 | }); 238 | 239 | $document.on("mousemove", self.drag); 240 | $document.on("mouseup", self.dragEnd); 241 | }, 242 | 243 | /** 244 | * @ngdoc function 245 | * @module ui.drop 246 | * @name ui.drop.$drag.Draggable#dragEnd 247 | * @methodOf ui.drop.$drag.Draggable 248 | * @function 249 | * 250 | * @param {MouseEvent} event The event to which dragEnd is responding 251 | * 252 | * @description 253 | * dragEnd is used to terminate a mouse drag. This is typically called in response to 254 | * a {@link https://developer.mozilla.org/en-US/docs/Web/Reference/Events/mouseup mouseup} 255 | * event on the document. 256 | * 257 | * This method essentially delegates functionality to the {@link ui.drop.$drag $drag} provider 258 | * in order to find the appropriate droppable element (if any) to drop over, and attempt to 259 | * place the element. 260 | * 261 | * Like {@link ui.drop.$drag.Draggable#dragStart dragStart()}, there are several properties 262 | * which are expected to be present in the event object. 263 | * {@link ui.drop.$drop#drop $drop.drop()} makes use of 264 | * {@link https://developer.mozilla.org/en-US/docs/Web/API/event.clientX event.clientX} and 265 | * {@link https://developer.mozilla.org/en-US/docs/Web/API/event.clientY event.clientY} if 266 | * they are available, which they should be in a mouse event. 267 | */ 268 | dragEnd: function(event) { 269 | event.preventDefault(); 270 | $document.off("mousemove", self.drag); 271 | $document.off("mouseup", self.dragEnd); 272 | 273 | $drop.drop(event); 274 | }, 275 | 276 | /** 277 | * @ngdoc function 278 | * @module ui.drop 279 | * @name ui.drop.$drag.Draggable#drag 280 | * @methodOf ui.drop.$drag.Draggable 281 | * @function 282 | * 283 | * @param {MouseEvent} event The event to which dragEnd is responding 284 | * 285 | * @description 286 | * drag is used to continue a mouse drag, and is typically called in response to a mousemove 287 | * event. 288 | * 289 | * Like {@link ui.drop.$drag.Draggable#dragStart dragStart()}, there are several properties 290 | * which are expected to be present in the event object. 291 | * {@link https://developer.mozilla.org/en-US/docs/Web/API/event.pageX event.pageX} and 292 | * {@link https://developer.mozilla.org/en-US/docs/Web/API/event.pageY event.pageY} are used 293 | * to determine the relative position from the last mouse position, and must be present in 294 | * the event. 295 | * 296 | * This method functions simply by adding the difference in mouse coordinates between the 297 | * last cached position with the current absolute position of the element, and sets the new 298 | * position as an absolute position to the element's style. 299 | */ 300 | drag: function(event) { 301 | event = fixup(event); 302 | event.preventDefault(); 303 | 304 | var self = currentDrag; 305 | if (!self) { 306 | return; 307 | } 308 | 309 | var style = self.element.prop('style'); 310 | 311 | var position = { 312 | top: event.pageY, 313 | left: event.pageX 314 | }, 315 | x = style.left || 0, y = style.top || 0, nx, ny; 316 | 317 | ny = parseInt(y, 10) + (position.top - self.lastMouseY); 318 | nx = parseInt(x, 10) + (position.left - self.lastMouseX); 319 | 320 | if (!self.constraints || withinConstraints(self.constraints, nx, ny, 321 | self.dimensions.width, self.dimensions.height)) { 322 | style.left = nx + "px"; 323 | style.top = ny + "px"; 324 | } 325 | 326 | self.lastMouseY = position.top; 327 | self.lastMouseX = position.left; 328 | }, 329 | 330 | /** 331 | * @ngdoc function 332 | * @module ui.drop 333 | * @name ui.drop.$drag.Draggable#finish 334 | * @methodOf ui.drop.$drag.Draggable 335 | * @function 336 | * 337 | * @description 338 | * The finish method is a simple helper, called by 339 | * {@link ui.drop.$drop.Droppable#drop Droppable.drop()} in order to terminate the dragging 340 | * interaction. 341 | * 342 | * This method simply restores the original css `position` property to its original pre-drag 343 | * value, and clears the currentDrag value. 344 | * 345 | * This should be considered a private method for the time being, because it is likely to 346 | * be removed from {@link ui.drop.$drag.Draggable Draggable} in the near future. 347 | */ 348 | finish: function() { 349 | this.element.css({ 350 | position: this.cssPosition 351 | }); 352 | this.dimensions = undefined; 353 | currentDrag = undefined; 354 | } 355 | }; 356 | 357 | function dragConstraints(value, element) { 358 | if (value.length === '') { 359 | return; 360 | } 361 | if (/^(\.|#)/.test(value) && $.fn && angular.isFunction($.fn.closest)) { 362 | // Find nearest element matching class 363 | return constraintsFor(element.parent().closest(value)); 364 | } 365 | if (/^(\^|parent)$/.test(value)) { 366 | return constraintsFor(element.parent()); 367 | } 368 | return constraintsFor($dndDOM.closest(element.parent(), value)); 369 | }; 370 | 371 | function constraintsFor(element) { 372 | if (typeof element === 'undefined' || element.length === 0) { 373 | return; 374 | } 375 | // Use offset + width/height for now? 376 | var constraints = $dndDOM.offset(element), 377 | dimensions = $dndDOM.size(element); 378 | constraints.right = constraints.left + dimensions.width; 379 | constraints.bottom = constraints.top + dimensions.height; 380 | return constraints; 381 | }; 382 | 383 | /** 384 | * @ngdoc property 385 | * @module ui.drop 386 | * @name ui.drop.$drag#current 387 | * @propertyOf ui.drop.$drag 388 | * @returns {ui.drop.$drag.Draggable} Draggable instance representing the currently dragged 389 | * element. 390 | * 391 | * @description 392 | * The current {@link ui.drop.$drag.Draggable Draggable}, which is being dragged at the given 393 | * moment, or undefined. 394 | */ 395 | readonly($drag, 'current', function() { return currentDrag; }); 396 | 397 | /** 398 | * @ngdoc property 399 | * @module ui.drop 400 | * @name ui.drop.$drag#version 401 | * @propertyOf ui.drop.$drag 402 | * @returns {ui.drop.Version} Version 403 | * 404 | * @description 405 | * A reference to the global {@link ui.drop.Version} object. 406 | */ 407 | readonly($drag, 'version', function() { return _version; }); 408 | 409 | return $drag; 410 | }]; 411 | 412 | /** 413 | * @ngdoc property 414 | * @module ui.drop 415 | * @name ui.drop.$dragProvider#version 416 | * @propertyOf ui.drop.$dragProvider 417 | * @returns {ui.drop.Version} Version 418 | * 419 | * @description 420 | * A reference to the global {@link ui.drop.Version} object. 421 | */ 422 | readonly(this, 'version', function() { return _version; }); 423 | 424 | function fixup(event) { 425 | if (angular.isUndefined(event)) { 426 | event = window.event || {pageX: 0, pageY: 0, preventDefault: function() {}}; 427 | } 428 | if (!angular.isFunction(event.preventDefault)) { 429 | event.preventDefault = function() {}; 430 | } 431 | return event; 432 | } 433 | 434 | function isDraggable(thing) { 435 | return angular.isObject(thing) && thing.constructor === Draggable; 436 | } 437 | 438 | function withinConstraints(c, x, y, w, h) { 439 | return x >= c.left && (x+w) <= c.right && y >= c.top && (y+h) <= c.bottom; 440 | } 441 | }; 442 | -------------------------------------------------------------------------------- /src/drop.prefix: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @license AngularDrop v"DROP_VERSION_FULL" 5 | * (c) 2013 Caitlin Potter & Contributors. http://caitp.github.io/angular-drop 6 | * License: MIT 7 | */ 8 | (function(window, document, undefined) { 9 | -------------------------------------------------------------------------------- /src/drop.suffix: -------------------------------------------------------------------------------- 1 | 2 | publishExternalAPI(); 3 | 4 | })(window, document); 5 | -------------------------------------------------------------------------------- /src/droppable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * @ngdoc directive 6 | * @module ui.drop 7 | * @name ui.drop.directive:droppable 8 | * 9 | * @description 10 | * 11 | * Simple directive which denotes a 'droppable' widget (an area onto which adraggable may be dropped). 12 | * 13 | * @param {string=} drop-allowed provides an array of element selector constraints for which all draggables must contain 14 | * in order to be dropped within this droppable. If no drop-allowed is provided, all draggables will be allowed. To 15 | * specify multiple selectors list them separated by commas (drop-allowed="div.class-one, div#id-two"). If jQuery is 16 | * present $.fn.is is used otherwise Element.matches selector functions are used (vendor prefixed). If Element.matches 17 | * functionality is not present class checking is used (element.hasClass). 18 | * 19 | */ 20 | var droppableDirective = ['$drop', '$parse', function($drop, $parse) { 21 | return { 22 | restrict: 'A', 23 | compile: function($element, $attrs) { 24 | return function(scope, element, attrs) { 25 | var allowed = $parse(attrs.dropAllowed || 'undefined')(scope); 26 | var droppable = $drop.droppable(element); 27 | if (allowed) { 28 | droppable.allowedSelectors(allowed); 29 | } 30 | } 31 | } 32 | }; 33 | }]; 34 | 35 | /** 36 | * @ngdoc object 37 | * @module ui.drop 38 | * @name ui.drop.$dropProvider 39 | * 40 | * @description 41 | * 42 | * TODO: enable the configuration of default Draggable options in $dragProvider. 43 | */ 44 | var $dropProvider = function() { 45 | /** 46 | * @ngdoc object 47 | * @module ui.drop 48 | * @name ui.drop.$drop 49 | * @requires $document 50 | * @requires $rootScope 51 | * 52 | * @description 53 | * 54 | * Service responsible for controlling the behaviour of droppable nodes. $drop provides 55 | * a mechanism to drop-enable any arbitrary element, which allows it to be used in 56 | * custom directives, so that custom dragging behaviour can be achieved. 57 | */ 58 | this.$get = ['$document', '$rootScope', function($document, $rootScope) { 59 | var $drop = { 60 | /** 61 | * @ngdoc method 62 | * @module ui.drop 63 | * @name ui.drop.$drop#isDroppable 64 | * @methodOf ui.drop.$drop 65 | * @returns {boolean} The draggable status of an element (true if an element is 66 | * drag-enabled, otherwise false) 67 | * 68 | * @description 69 | * 70 | * Query the droppable status of an element. Droppable in this context means 71 | * that the element has Droppable state attached to it, and does not currently 72 | * include other factors which might enable or disable the dropping a node into an 73 | * element. 74 | */ 75 | isDroppable: function(element) { 76 | return !!$drag.droppable(element, false); 77 | }, 78 | 79 | /** 80 | * @ngdoc method 81 | * @module ui.drop 82 | * @name ui.drop.$drop#droppable 83 | * @methodOf ui.drop.$drop 84 | * @returns {Object} The Droppable state bound to the element. 85 | * 86 | * @param {element} element jQuery / jqLite element or DOM node to be checked for 87 | * Droppable state, or to be the element to which a new Droppable state is associated. 88 | * @param {object|boolean} options Configuration options for the Droppable state to be 89 | * created. If set to false, no Droppable state will be created, and the function 90 | * instead acts as a simple query. 91 | * 92 | * @description 93 | * 94 | * Queries for the Droppable state of a DOM node. If the element is not 95 | * Droppable, and `options !== false`, then a new Droppable object is instantiated 96 | * and associated with the element. 97 | * 98 | * As such, this method can be used to query for the existence of Droppable state 99 | * attached to a DOM node, similar to {@link ui.drop.$drop#isDroppable isDroppable()}. 100 | * 101 | * TODO: Control actual behaviour of Droppable state with passed in options. 102 | */ 103 | droppable: function(element, options) { 104 | element = angular.element(element); 105 | 106 | var $droppable = element.data("$droppable"); 107 | 108 | if ($droppable) { 109 | return $droppable; 110 | } 111 | 112 | if (options === false) { 113 | return undefined; 114 | } 115 | 116 | options = angular.extend({ 117 | constraints: null, 118 | delay: 500, 119 | distance: 1 120 | }, options || {}); 121 | 122 | $droppable = new Droppable(); 123 | 124 | $droppable.element = element; 125 | $droppable.options = options; 126 | 127 | element.data('$droppable', $droppable); 128 | 129 | return $droppable; 130 | }, 131 | 132 | /** 133 | * @ngdoc method 134 | * @module ui.drop 135 | * @name ui.drop.$drop#drop 136 | * @methodOf ui.drop.$drop 137 | * 138 | * @param {Event|number} event Either a DOM Event object which contains client coordinates 139 | * of a mouse release, or else the x coordinate at which the mouse was released. 140 | * @param {number=} y The second parameter may serve as the y coordinate at which the mouse 141 | * was released, and is expected to be if `event` is a number. 142 | * 143 | * @description 144 | * Given a client position at which a mouse was released, or a mouse release is being 145 | * simulated, this method attempts to find the nearest Droppable element, onto which the 146 | * dragged element shall be dropped. 147 | * 148 | * The css display of the dragged element is set to 'none' so that 149 | * {@link https://developer.mozilla.org/en-US/docs/Web/API/document.elementFromPoint 150 | * document.elementFromPoint()} will not always return the dragged element. 151 | * 152 | * If implementing custom drag/drop functionality, it is important to ensure that the 153 | * non-hidden css display be restored when finished. 154 | */ 155 | drop: function(x, y) { 156 | if (!currentDrag) { 157 | return; 158 | } 159 | if (typeof x === 'undefined') { 160 | x = window.event; 161 | } 162 | if (angular.isObject(x)) { 163 | // This might be an event object 164 | if (typeof x.clientX === 'number' && typeof x.clientY === 'number') { 165 | y = x.clientY; 166 | x = x.clientX; 167 | } else if (typeof x.left === 'number' && typeof x.top === 'number') { 168 | y = x.top; 169 | x = x.left; 170 | } 171 | } 172 | if (typeof x !== 'number' || typeof y !== 'number') { 173 | return; 174 | } 175 | var current = currentDrag, element, $droppable; 176 | 177 | // Element must be hidden so that elementFromPoint can find the appropriate element. 178 | current.element.css({ 179 | display: 'none' 180 | }); 181 | 182 | element = document.elementFromPoint(x, y); 183 | if (!element) { 184 | return badDrop(); 185 | } 186 | if (element.nodeType === 3) { 187 | // Opera 188 | element = element.parentNode; 189 | } 190 | element = angular.element(element); 191 | $droppable = element.inheritedData('$droppable'); 192 | 193 | if (!$droppable || !this.dropAllowed($droppable, current.element)) { 194 | // Element is not droppable... 195 | return badDrop(); 196 | } 197 | 198 | $droppable.drop(current); 199 | 200 | return true; 201 | 202 | function badDrop() { 203 | current.hanging = true; 204 | current.element.css({ 205 | display: current.cssDisplay 206 | }); 207 | currentDrag = undefined; 208 | $rootScope.$emit("$badDrop", current); 209 | } 210 | }, 211 | 212 | /** 213 | * @ngdoc method 214 | * @module ui.drop 215 | * @name ui.drop.$drop#dropAllowed 216 | * @methodOf ui.drop.$drop 217 | * 218 | * @param {droppable} The ui.drop.$drop.Droppable object 219 | * @param {draggable} An angular.element() object representing the draggable 220 | * @returns {boolean} whether or not the drop is allowed based 221 | * 222 | * @description 223 | * Function to check if the provided draggable element has the class of the provided droppables 'allowed' 224 | * attribute. Returns true if a match is found, or false otherwise. 225 | * 226 | */ 227 | dropAllowed: function(droppable, draggable) { 228 | var allowed = droppable.allowedSelectors(); 229 | if (!allowed || !angular.isArray(allowed)) { 230 | return true; 231 | } 232 | 233 | for (var i = 0; i < allowed.length; ++i) { 234 | var curAllowed = allowed[i]; 235 | if (typeof curAllowed === 'string') { 236 | if (matchesSelector(draggable, curAllowed)) { 237 | return true; 238 | } 239 | } 240 | } 241 | return false; 242 | } 243 | }; 244 | 245 | /** 246 | * @ngdoc function 247 | * @module ui.drop 248 | * @name ui.drop.$drop.Droppable 249 | * 250 | * @returns {Object} Newly created Droppable instance. 251 | * 252 | * @description 253 | * Droppable state is an object containing the droppable state of a particular DOM node. 254 | * It is instantiated and attached via {@link ui.drop.$drop#droppable droppable()}. 255 | * 256 | * The Droppable object is attached to a DOM node via jqLite / jQuery's expandostore, 257 | * using the key `$droppable`. However, it is recomended that querying for a node's 258 | * Droppable state be done using {@link ui.drop.$drop#droppable droppable()} or 259 | * {@link ui.drop.$drop#isDroppable isDroppable()}, in case the expandostore key is 260 | * changed in the future. 261 | * 262 | * This object provides helpers, including a mechanism to programmatically drop an 263 | * arbitrary Draggable onto the current Droppable. This mechanic is used by 264 | * {@link ui.drop.$drop#drop $drop.drop()} in order to complete the work. 265 | */ 266 | Droppable.prototype = { 267 | constructor: Droppable, 268 | 269 | /** 270 | * @ngdoc function 271 | * @module ui.drop 272 | * @name ui.drop.Droppable#drop 273 | * @methodOf ui.drop.$drop.Droppable 274 | * @function 275 | * 276 | * @param {Draggable=} draggable The Draggable state of the dragged element, or 277 | * the current dragging object by default. 278 | * @param {options=} options Presently this parameter is essentially unused. It is 279 | * intended to enable some customized behaviour in the future. 280 | * 281 | * @description 282 | * Simplifies the process of dropping a dragged element over a $droppable element, 283 | * and appending it to the $droppable node's children. 284 | */ 285 | drop: function(draggable, options) { 286 | draggable = draggable || currentDrag; 287 | if (typeof draggable.length === 'number' || draggable.nodeName) { 288 | // Looks like an element... 289 | draggable = angular.element(draggable).data('$draggable'); 290 | } 291 | if (!draggable || draggable.constructor !== Draggable) { 292 | return; 293 | } 294 | 295 | options = angular.extend(options || {}, { 296 | display: draggable.cssDisplay 297 | }); 298 | 299 | this.element.append(draggable.element); 300 | 301 | if (draggable.options.keepSize) { 302 | draggable.element.css(draggable.keepSize); 303 | draggable.keepSize = undefined; 304 | } 305 | 306 | draggable.element.css({ 307 | display: options.display 308 | }); 309 | draggable.hanging = false; 310 | if (!$rootScope.$$phase) { 311 | $rootScope.$apply(); 312 | } 313 | draggable.finish(); 314 | }, 315 | 316 | /** 317 | * @ngdoc function 318 | * @module ui.drop 319 | * @name ui.drop.Droppable#allowedSelectors 320 | * @methodOf ui.drop.$drop.Droppable 321 | * @function 322 | * 323 | * @param {allowedSelectors} An array of strings representing selectors of draggables which can be 324 | * dropped within the draggable 325 | * @returns {Array} Array of strings 326 | * 327 | * @description 328 | * A Setter/Getter method for the array of allowed selectors for this droppable. 329 | */ 330 | allowedSelectors: function(allowedSelectors) { 331 | if (arguments.length > 0) { 332 | if (typeof allowedSelectors === 'string') { 333 | allowedSelectors = allowedSelectors.split(','); 334 | } 335 | if (angular.isArray(allowedSelectors)) { 336 | this.allowed = allowedSelectors; 337 | } 338 | return this; 339 | } 340 | return this.allowed; 341 | } 342 | }; 343 | 344 | /** 345 | * @ngdoc property 346 | * @module ui.drop 347 | * @name ui.drop.$drop#version 348 | * @propertyOf ui.drop.$drop 349 | * @returns {ui.drop.Version} Version 350 | * 351 | * @description 352 | * A reference to the global {@link ui.drop.Version} object. 353 | */ 354 | readonly($drop, 'version', function() { return _version; }); 355 | 356 | return $drop; 357 | }]; 358 | 359 | /** 360 | * @ngdoc property 361 | * @module ui.drop 362 | * @name ui.drop.$dropProvider#version 363 | * @propertyOf ui.drop.$dropProvider 364 | * @returns {ui.drop.Version} Version 365 | * 366 | * @description 367 | * A reference to the global {@link ui.drop.Version} object. 368 | */ 369 | readonly(this, 'version', function() { return _version; }); 370 | }; 371 | -------------------------------------------------------------------------------- /src/provider.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc object 5 | * @module ui.drop 6 | * @name ui.drop.$dndProvider 7 | * 8 | * @description 9 | * A configuration provider which is intended to combine access to both 10 | * $drag and $drop during configuration, so that only a single provider 11 | * need be injected. 12 | */ 13 | var $dndProvider = function() { 14 | /** 15 | * @ngdoc object 16 | * @module ui.drop 17 | * @name ui.drop.$dnd 18 | * 19 | * @description 20 | * 21 | * TODO: Enable access to $drag and $drop in $dnd. 22 | */ 23 | this.$get = [function() { 24 | var $dnd = { 25 | 26 | }; 27 | 28 | /** 29 | * @ngdoc property 30 | * @module ui.drop 31 | * @name ui.drop.$dnd#current 32 | * @propertyOf ui.drop.$dnd 33 | * @returns {ui.drop.$drag.Draggable} Draggable instance representing the currently dragged 34 | * element. 35 | * 36 | * @description 37 | * The current {@link ui.drop.$drag.Draggable Draggable}, which is being dragged at the given 38 | * moment, or undefined. 39 | */ 40 | readonly($dnd, 'current', function() { return currentDrag; }); 41 | 42 | /** 43 | * @ngdoc property 44 | * @module ui.drop 45 | * @name ui.drop.$dnd#version 46 | * @propertyOf ui.drop.$dnd 47 | * @returns {ui.drop.Version} Version 48 | * 49 | * @description 50 | * A reference to the global {@link ui.drop.Version} object. 51 | */ 52 | readonly($dnd, 'version', function() { return _version; }); 53 | 54 | return $dnd; 55 | }]; 56 | 57 | /** 58 | * @ngdoc property 59 | * @module ui.drop 60 | * @name ui.drop.$dndProvider#version 61 | * @propertyOf ui.drop.$dndProvider 62 | * @returns {ui.drop.Version} Version 63 | * 64 | * @description 65 | * A reference to the global {@link ui.drop.Version} object. 66 | */ 67 | readonly(this, 'version', function() { return _version; }); 68 | }; 69 | -------------------------------------------------------------------------------- /src/public.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function publishExternalAPI() { 4 | angular.module('ui.drop', [], ['$provide', function($provide) { 5 | $provide.provider({ 6 | $dnd: $dndProvider, 7 | $drag: $dragProvider, 8 | $drop: $dropProvider 9 | }); 10 | }]).directive({ 11 | draggable: draggableDirective, 12 | droppable: droppableDirective 13 | }).factory({ 14 | $dndDOM: $dndDOMFactory 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /src/publish.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | publishExternalAPI(); 4 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc object 3 | * @module ui.drop 4 | * @name ui.drop.Version 5 | * @description 6 | * An object that contains information about the current Angular-Drop version. This object has the 7 | * following properties: 8 | * 9 | * - `full` – `{string}` – Full version string, such as "0.9.18". 10 | * - `major` – `{number}` – Major version number, such as "0". 11 | * - `minor` – `{number}` – Minor version number, such as "9". 12 | * - `dot` – `{number}` – Dot version number, such as "18". 13 | * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". 14 | */ 15 | var _version = { 16 | full: '"DROP_VERSION_FULL"', // all of these placeholder strings will be replaced by grunt's 17 | major: '"DROP_VERSION_MAJOR"', // package task 18 | minor: '"DROP_VERSION_MINOR"', 19 | dot: '"DROP_VERSION_DOT"', 20 | codeName: '"DROP_VERSION_CODENAME"' 21 | }, 22 | 23 | currentDrag, 24 | 25 | booleans = { 26 | 'false': false, 27 | 'true': true, 28 | '': true 29 | }, 30 | 31 | unconst = function(value) { 32 | if (typeof value === 'string') { 33 | var num; 34 | if (booleans.hasOwnProperty(value)) { 35 | return booleans[value]; 36 | } else { 37 | // TODO: Support floats? 38 | num = parseInt(value, 10); 39 | if (num===num) { 40 | value = num; 41 | } 42 | } 43 | } 44 | return value; 45 | }; 46 | 47 | function Draggable() {}; 48 | function Droppable() {}; 49 | 50 | var 51 | readonly = function(target, name, fn) { 52 | if (Object.defineProperty) { 53 | Object.defineProperty(target, name, { 54 | get: fn, 55 | set: function() {}, 56 | configurable: false, 57 | enumerable: true 58 | }); 59 | } else if (target.__defineGetter__) { 60 | target.__defineGetter__(name, fn); 61 | } else { 62 | target[name] = fn(); 63 | } 64 | }, 65 | 66 | matchesFn, 67 | CLASS_SELECTOR_REGEXP = 68 | /^(\s*(\.-?([a-z\u00A0-\u10FFFF]|(\\\d+))([0-9a-z\u00A0-\u10FFFF_-]|(\\\d+))*)\s*)+$/i, 69 | 70 | getMatchesFn = function() { 71 | var selectorFunctions = ['matches', 'matchesSelector', 'msMatchesSelector', 'mozMatchesSelector', 72 | 'webkitMatchesSelector', 'oMatchesSelector']; 73 | 74 | if (typeof window.Element === 'function' && typeof window.Element.prototype === 'object') { 75 | for (var i=0, ii=selectorFunctions.length; i < ii; ++i) { 76 | var name = selectorFunctions[i]; 77 | if (typeof window.Element.prototype[name] === 'function') { 78 | var matches = window.Element.prototype[name]; 79 | return function(jq, selector) { 80 | return matches.call(jq[0], selector); 81 | } 82 | } 83 | } 84 | } 85 | if (typeof $ === 'function' && typeof $.prototype.is === 'function') { 86 | return function(jq, selector) { 87 | return jq.is(selector); 88 | } 89 | } else if (typeof Sizzle === 'function' && typeof Sizzle.matchesSelector === 'function') { 90 | return function(jq, selector) { 91 | return Sizzle.matchesSelector(jq[0], selector); 92 | } 93 | } else { 94 | // Default case: throw if any non-class selectors are used. 95 | return function(jq, selector) { 96 | if (selector && CLASS_SELECTOR_REGEXP.test(selector)) { 97 | selector = selector.replace(/\s+/g, '').replace('.', ' ').replace(/^\s+/, '').replace(/\s+$/, ''); 98 | return jq.hasClass(selector); 99 | } else { 100 | throw new Error("Only class-based selectors are supported in this browser."); 101 | } 102 | } 103 | } 104 | }, 105 | 106 | matchesSelector = function(node, selector) { 107 | var domEle; 108 | 109 | if (typeof matchesFn !== 'function') { 110 | matchesFn = getMatchesFn(); 111 | } 112 | 113 | return matchesFn(node, selector); 114 | }; 115 | -------------------------------------------------------------------------------- /test/directive/draggable.spec.js: -------------------------------------------------------------------------------- 1 | describe('draggable directive', function() { 2 | var element, $drag, $drop, $dnd, $compile, $rootScope; 3 | beforeEach(module('ui.drop')); 4 | beforeEach(inject(function(_$drag_, _$drop_, _$dnd_, _$compile_, _$rootScope_) { 5 | $drag = _$drag_; 6 | $drop = _$drop_; 7 | $dnd = _$dnd_; 8 | $compile = _$compile_; 9 | $rootScope = _$rootScope_; 10 | })); 11 | afterEach(function() { 12 | if (element) { 13 | element = undefined; 14 | } 15 | }); 16 | 17 | it('should instantiate $draggable instance', function() { 18 | element = $compile('
')($rootScope); 19 | expect($drag.draggable(element, false)).toBeDefined(); 20 | }); 21 | 22 | angular.forEach({ 23 | '': true, 'true': true, 'false': false, '0': false, '1': true 24 | }, function(expected, value) { 25 | it('should set the `keepSize` option to '+expected+' with attribute="'+value+'"', function() { 26 | element = $compile('
')($rootScope); 27 | expect(!!$drag.draggable(element, false).options.keepSize).toEqual(expected); 28 | }); 29 | }); 30 | angular.forEach({ 31 | true: true, false: false, 0: false, 1: true 32 | }, function(expected, value) { 33 | it('should set the `keepSize` option to '+expected+' with interpolated attribute valued at '+ 34 | value, function() { 35 | $rootScope.val = value; 36 | element = $compile('
')($rootScope); 37 | expect(!!$drag.draggable(element, false).options.keepSize).toEqual(expected); 38 | }); 39 | }); 40 | 41 | it('should set the `dragWithin` option to the passed string', function() { 42 | element = $compile('
')($rootScope); 43 | expect($drag.draggable(element, false).options.dragWithin).toEqual('body'); 44 | }); 45 | 46 | 47 | it('should set the `dragWithin` option to the interpolated attribute value', function() { 48 | $rootScope.dragWithin = "body"; 49 | element = $compile('
')($rootScope); 50 | expect($drag.draggable(element, false).options.dragWithin).toEqual('body'); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/directive/droppable.spec.js: -------------------------------------------------------------------------------- 1 | describe('droppable directive', function() { 2 | var element, $drag, $drop, $dnd, $compile, $rootScope; 3 | beforeEach(module('ui.drop')); 4 | beforeEach(inject(function(_$drag_, _$drop_, _$dnd_, _$compile_, _$rootScope_) { 5 | $drag = _$drag_; 6 | $drop = _$drop_; 7 | $dnd = _$dnd_; 8 | $compile = _$compile_; 9 | $rootScope = _$rootScope_; 10 | })); 11 | afterEach(function() { 12 | if (element) { 13 | element = undefined; 14 | } 15 | }); 16 | 17 | it('should instantiate $droppable instance', function() { 18 | element = $compile('
')($rootScope); 19 | expect($drop.droppable(element, false)).toBeDefined(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/jquery_alias.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _jQuery = jQuery, 4 | _jqLiteMode = false; 5 | -------------------------------------------------------------------------------- /test/jquery_remove.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _jQuery = jQuery.noConflict(true), 4 | _jqLiteMode = true; 5 | -------------------------------------------------------------------------------- /test/provider/dnd.spec.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caitp/angular-drop/be9c9569b71aeb00043a4c51931a00d5783c2bb2/test/provider/dnd.spec.js -------------------------------------------------------------------------------- /test/provider/drag.spec.js: -------------------------------------------------------------------------------- 1 | describe('$drag', function() { 2 | var element, $drag, $drop, $dnd, $compile, $rootScope; 3 | beforeEach(module('ui.drop')); 4 | beforeEach(inject(function(_$drag_, _$drop_, _$dnd_, _$compile_, _$rootScope_) { 5 | $drag = _$drag_; 6 | $drop = _$drop_; 7 | $dnd = _$dnd_; 8 | $compile = _$compile_; 9 | $rootScope = _$rootScope_; 10 | element = angular.element('
'); 11 | })); 12 | afterEach(function() { 13 | if (element) { 14 | element = undefined; 15 | } 16 | }); 17 | 18 | describe('draggable()', function() { 19 | it('should instantiate Draggable', function() { 20 | var instance = $drag.draggable(element); 21 | expect(instance.constructor.toString()).toContain("Draggable"); 22 | }); 23 | 24 | it('should return the already instantiated Draggable instance on subsequent calls', function() { 25 | var instance = $drag.draggable(element); 26 | expect($drag.draggable(element)).toEqual(instance); 27 | }); 28 | 29 | it('should not instantiate Draggable if second parameter === false', function() { 30 | expect($drag.draggable(element, false)).toBeUndefined(); 31 | }); 32 | }); 33 | 34 | it('should set $drag.current to the dragging element on mousedown', function() { 35 | var instance = $drag.draggable(element); 36 | element.triggerHandler('mousedown'); 37 | expect($drag.current).toEqual(instance); 38 | }); 39 | 40 | describe('DOM manipulation', function() { 41 | var parent, $body; 42 | beforeEach(inject(function(_$document_) { 43 | $body = _$document_.find('body'); 44 | $body.css({ 45 | margin: '0', 46 | padding: '0' 47 | }); 48 | })); 49 | beforeEach(function() { 50 | parent = angular.element('
'); 51 | parent.append(element); 52 | $body.append(parent); 53 | }); 54 | afterEach(function() { 55 | parent.remove(); 56 | parent = undefined; 57 | }); 58 | it('should keep a dragged elements size when keepSize is true', function() { 59 | var draggable = $drag.draggable(element, { keepSize: true }), style = element.prop('style'); 60 | draggable.dragStart(); 61 | expect(style.width).toEqual('100px'); 62 | expect(style.height).toEqual('100px'); 63 | }); 64 | 65 | 66 | it('should keep element within constraints when dragWithin is set', function() { 67 | element.css({ height: '1px', width: '1px' }); 68 | var draggable = $drag.draggable(element, { dragWithin: 'parent' }), 69 | style = element.prop('style'); 70 | draggable.dragStart(); 71 | 72 | draggable.drag({pageX: 99, pageY: 99}); 73 | // Position changes due to being within constrained area 74 | expect(parseInt(style.left, 10)).toEqual(99); 75 | expect(parseInt(style.top, 10)).toEqual(99); 76 | 77 | draggable.drag({pageX: 101, pageY: 101}); 78 | // Position remains same due to being dragged outside of constrained area 79 | expect(parseInt(style.left, 10)).toEqual(99); 80 | expect(parseInt(style.top, 10)).toEqual(99); 81 | }); 82 | 83 | it ('should offset element relative to nearest positioned ancestor', function() { 84 | 85 | parent.css({position:'relative', top:'100px', left:'100px'}); 86 | var draggable = $drag.draggable(element, {keepSize:true}), 87 | style = element.prop('style'); 88 | draggable.dragStart(); 89 | 90 | expect(parseInt(style.left, 10)).toBe(0); 91 | expect(parseInt(style.top, 10)).toBe(0); 92 | }); 93 | 94 | it ('should offset element relative to body if no positioned ancestor', function() { 95 | 96 | parent.css({position:'static', margin:'100px'}); 97 | var draggable = $drag.draggable(element, {keepSize:true}), 98 | style = element.prop('style'); 99 | draggable.dragStart(); 100 | 101 | expect(parseInt(style.left, 10)).toBe(100); 102 | expect(parseInt(style.top, 10)).toBe(100); 103 | }); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /test/provider/drop.spec.js: -------------------------------------------------------------------------------- 1 | describe('$drop', function() { 2 | var element, $drag, $drop, $dnd, $compile, $rootScope, hasMatchesSelector = false; 3 | var selectorFunctions = ['matches', 'matchesSelector', 'msMatchesSelector', 'mozMatchesSelector', 4 | 'webkitMatchesSelector', 'oMatchesSelector']; 5 | 6 | if (typeof window.Element === 'function' && typeof window.Element.prototype === 'object') { 7 | for (var i=0, ii=selectorFunctions.length; i'); 23 | })); 24 | afterEach(function() { 25 | if (element) { 26 | element = undefined; 27 | } 28 | }); 29 | 30 | describe('droppable()', function() { 31 | it('should instantiate Droppable', function() { 32 | var instance = $drop.droppable(element); 33 | expect(instance.constructor.toString()).toContain("Droppable"); 34 | }); 35 | 36 | it('should return the already instantiated Droppable instance on subsequent calls', function() { 37 | var instance = $drop.droppable(element); 38 | expect($drop.droppable(element)).toEqual(instance); 39 | }); 40 | 41 | it('should not instantiate Droppable if second parameter === false', function() { 42 | expect($drop.droppable(element, false)).toBeUndefined(); 43 | }); 44 | }); 45 | 46 | describe('Droppable#drop()', function() { 47 | it('should append dragged element', function() { 48 | var d0 = $compile('
')($rootScope), 49 | d1 = d0.children('div').eq(0), 50 | d2 = d1.children('div').eq(0), 51 | droppable = $drop.droppable(d1); 52 | $drag.draggable(element); 53 | droppable.drop(element); 54 | 55 | expect(d1.children().length).toEqual(2); 56 | expect(d1.children().eq(1).prop('id')).toEqual('draggable'); 57 | }); 58 | }); 59 | 60 | describe('dropAllowed()', function() { 61 | if (window.jQuery || hasMatchesSelector) { 62 | it ('should return true if the draggable matches the droppable allowed selectors', 63 | function() { 64 | var draggable = angular.element('
'), 65 | droppable = $drop.droppable(angular.element('
')); 66 | droppable.allowedSelectors(['allow-you', 'div#allow-me', 'allow-another']); 67 | expect($drop.dropAllowed(droppable, draggable)).toBeTruthy(); 68 | }); 69 | 70 | it ('should return false if the draggable does not match the droppable allowed selectors', function() { 71 | var draggable = angular.element('
') 72 | droppable = $drop.droppable(angular.element('
')); 73 | droppable.allowedSelectors(['div.disallow-me']); 74 | expect($drop.dropAllowed(droppable, draggable)).toBeFalsy(); 75 | }); 76 | 77 | it ('should return true if the droppable does not have any allowed selectors', function() { 78 | var draggable = angular.element('
'), 79 | droppable = $drop.droppable(angular.element('
')); 80 | expect($drop.dropAllowed(droppable, draggable)).toBeTruthy(); 81 | }); 82 | } 83 | 84 | it ('should return true if the draggable matches the droppable class selector', function() { 85 | var draggable = angular.element('
'), 86 | droppable = $drop.droppable(angular.element('
')); 87 | droppable.allowedSelectors(['.allowed']); 88 | expect($drop.dropAllowed(droppable, draggable)).toBeTruthy(); 89 | }); 90 | 91 | it ('should return true if the draggable does not match the droppable allowed class selectors', function() { 92 | var draggable = angular.element('
'), 93 | droppable = $drop.droppable(angular.element('
')); 94 | droppable.allowedSelectors(['.allowed']); 95 | expect($drop.dropAllowed(droppable, draggable)).toBeFalsy(); 96 | }); 97 | 98 | it ('should return support class selectors with capital letters', function() { 99 | var draggable = angular.element('
'), 100 | droppable = $drop.droppable(angular.element('
')); 101 | droppable.allowedSelectors(['.alloweD']); 102 | expect($drop.dropAllowed(droppable, draggable)).toBeTruthy(); 103 | }); 104 | }); 105 | }); 106 | --------------------------------------------------------------------------------