├── requirements.txt ├── app ├── __init__.pyc ├── __pycache__ │ ├── models.cpython-35.pyc │ ├── views.cpython-35.pyc │ └── __init__.cpython-35.pyc ├── static │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.svg │ ├── js │ │ ├── lib │ │ │ ├── index.js │ │ │ ├── bootstrap-datetimepicker.zh-CN.js │ │ │ └── bootstrap-datetimepicker.js │ │ └── components │ │ │ ├── Footer.js │ │ │ ├── DataCharts.js │ │ │ ├── Pager.js │ │ │ ├── Header.js │ │ │ ├── CommentList.js │ │ │ ├── CompareChart.js │ │ │ ├── RadarChart.js │ │ │ └── LineChart.js │ ├── less │ │ └── index.less │ └── css │ │ ├── index.css │ │ └── bootstrap-datetimepicker.css ├── __init__.py ├── templates │ ├── index.html │ ├── login.html │ └── signup.html ├── models.py └── views.py ├── .gitignore ├── config.py ├── run.py ├── README.md ├── package.json └── gulpfile.js /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask 2 | flask-mongoengine 3 | Flask-Bcrypt 4 | flask-login -------------------------------------------------------------------------------- /app/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buptyyf/cartoonDataShow/HEAD/app/__init__.pyc -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /cartoon 2 | /.idea 3 | /__pycache__ 4 | basic_logger.log 5 | /node_modules 6 | 7 | -------------------------------------------------------------------------------- /app/__pycache__/models.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buptyyf/cartoonDataShow/HEAD/app/__pycache__/models.cpython-35.pyc -------------------------------------------------------------------------------- /app/__pycache__/views.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buptyyf/cartoonDataShow/HEAD/app/__pycache__/views.cpython-35.pyc -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | CSRF_ENABLED = True 2 | SECRET_KEY = 'UNICORN' 3 | MONGODB_SETTINGS = {'db': 'cartoon','host': '127.0.0.1','port': 27017} -------------------------------------------------------------------------------- /app/__pycache__/__init__.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buptyyf/cartoonDataShow/HEAD/app/__pycache__/__init__.cpython-35.pyc -------------------------------------------------------------------------------- /app/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buptyyf/cartoonDataShow/HEAD/app/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /app/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buptyyf/cartoonDataShow/HEAD/app/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /app/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buptyyf/cartoonDataShow/HEAD/app/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #!flask/bin/python 2 | from app import app 3 | 4 | if __name__ == '__main__': 5 | app.run(debug=True) 6 | 7 | 8 | #app.run(host='0.0.0.0',port=80) -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_mongoengine import MongoEngine 3 | import flask_login as flask_login 4 | 5 | 6 | app = Flask(__name__) 7 | app.config.from_object("config") 8 | 9 | db = MongoEngine(app) 10 | 11 | login_manager = flask_login.LoginManager() 12 | login_manager.init_app(app) 13 | 14 | from app import models, views -------------------------------------------------------------------------------- /app/static/js/lib/index.js: -------------------------------------------------------------------------------- 1 | var React = require("react"); 2 | var ReactDOM = require("react-dom"); 3 | var DataCharts = require("../components/DataCharts"); 4 | console.log(DataCharts); 5 | //React.render(React.createElement(DataCharts , null), document.getElementById("data-container")); 6 | ReactDOM.render(, document.getElementById("data-container")); 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cartoonDataShow 2 | 从各大动漫网站抓取到数据后存储在mongodb中,然后用python(flask)提供的路由和接口,在前端用react+echarts进行数据展示 3 | 4 | * 运行环境python3.4、mongodb 5 | * 配置方法: 6 | * windows用户可以在目录下执行python -m venv flask,得到flask框架然后再通过flask\Scripts\activate.bat或flask\Scripts\activate进入virtualenv虚拟环境。 7 | * 执行npm install,安装js依赖包,再执行pip install -r requirements.txt安装项目所需的python库,此时,你已经具备了运行整个工程的前提,然后再执行python run.py,通过访问localhost:5000就可以访问了 8 | 9 | #####因为一开始就需要登陆,所以可以先进http://localhost:5000/signup 进行注册 -------------------------------------------------------------------------------- /app/static/js/components/Footer.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ReactEcharts = require('echarts-for-react') 3 | 4 | var Footer = React.createClass({ 5 | 6 | render: function(){ 7 | return( 8 | 15 | ) 16 | } 17 | 18 | }); 19 | 20 | module.exports = Footer; -------------------------------------------------------------------------------- /app/static/less/index.less: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 80px; /* 100px is double the height of the navbar - I made it a big larger for some more space - keep it at 50px at least if you want to use the fixed top nav */ 3 | footer { 4 | margin: 50px 0; 5 | } 6 | .gap{ 7 | margin: 25px 0; 8 | } 9 | .title{ 10 | font-size: x-large; 11 | font-weight: bold; 12 | font-family: 'SimHei'; 13 | } 14 | hr { 15 | color: black; 16 | } 17 | .form-sign { 18 | max-width: 450px; 19 | padding: 20px; 20 | margin: 0 auto; 21 | } 22 | input { 23 | margin-bottom: 5px; 24 | } 25 | } 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/static/css/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 80px; 3 | /* 100px is double the height of the navbar - I made it a big larger for some more space - keep it at 50px at least if you want to use the fixed top nav */ 4 | } 5 | body footer { 6 | margin: 50px 0; 7 | } 8 | body .gap { 9 | margin: 25px 0; 10 | } 11 | body .title { 12 | font-size: x-large; 13 | font-weight: bold; 14 | font-family: 'SimHei'; 15 | } 16 | body hr { 17 | color: black; 18 | } 19 | body .form-sign { 20 | max-width: 450px; 21 | padding: 20px; 22 | margin: 0 auto; 23 | } 24 | body input { 25 | margin-bottom: 5px; 26 | } 27 | -------------------------------------------------------------------------------- /app/static/js/lib/bootstrap-datetimepicker.zh-CN.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simplified Chinese translation for bootstrap-datetimepicker 3 | * Yuan Cheung 4 | */ 5 | ;(function($){ 6 | $.fn.datetimepicker.dates['zh-CN'] = { 7 | days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"], 8 | daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日"], 9 | daysMin: ["日", "一", "二", "三", "四", "五", "六", "日"], 10 | months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"], 11 | monthsShort: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"], 12 | today: "今天", 13 | suffix: [], 14 | meridiem: ["上午", "下午"] 15 | }; 16 | }(jQuery)); 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ulab", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "babelify": "^7.3.0", 13 | "browserify": "^13.0.1", 14 | "gulp": "^3.9.1", 15 | "gulp-less": "^3.1.0", 16 | "echarts": "^3.1.1", 17 | "reactify": "^1.1.1", 18 | "vinyl-source-stream": "^1.1.0", 19 | "watchify": "^3.7.0" 20 | }, 21 | "dependencies": { 22 | "echarts-for-react": "^1.1.2", 23 | "react": "^0.14.0", 24 | "react-dom": "^0.14.0", 25 | "react-router": "^2.6.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/static/js/components/DataCharts.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Header = require('./Header'); 3 | var RadarChart = require('./RadarChart'); 4 | var LineChart = require('./LineChart'); 5 | var CompareChart = require('./CompareChart'); 6 | //var Footer = require('./Footer') 7 | var CommentList = require('./CommentList'); 8 | var Route = require('react-router').Route; 9 | var Router = require('react-router').Router; 10 | var history = require('react-router/lib/browserHistory'); 11 | /*var hashHistory = require('react-router')*/ 12 | 13 | var DataCharts = React.createClass({ 14 | getInitialState : function(){ 15 | return { 16 | date: '', 17 | cartoonName: '', 18 | webName: '' 19 | } 20 | }, 21 | componentDidMount: function(){ 22 | 23 | }, 24 | render: function(){ 25 | return( 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ) 35 | } 36 | }); 37 | 38 | module.exports = DataCharts; -------------------------------------------------------------------------------- /app/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | UNICORN 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 登陆UNICORN 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 |
18 |
19 |

欢迎登陆

20 | 21 | 22 | 23 |
24 |
25 | 26 | -------------------------------------------------------------------------------- /app/static/js/components/Pager.js: -------------------------------------------------------------------------------- 1 | var Addons = require("react/lib/ReactWithAddons"); 2 | var React = require('react'); 3 | 4 | var Pager = React.createClass({ 5 | getDefaultProps : function(){ 6 | return{ 7 | page:1, 8 | pages:1 9 | } 10 | }, 11 | clickHandler: function(e){ 12 | e.stopPropagation(); 13 | this.props.listComment(e.target.dataset.page); 14 | 15 | }, 16 | render : function(){ 17 | var cx = React.addons.createFragment; 18 | console.log(React); 19 | console.log(Addons); 20 | console.log(cx); 21 | if(this.props.page == 1) { 22 | var preClass = 'previous disabled' 23 | } else { 24 | var preClass = 'previous' 25 | } 26 | if(this.props.page == this.props.pages) { 27 | var nextClass = 'next disabled' 28 | } else { 29 | var nextClass = 'next' 30 | } 31 | /*var preClass = cx({ 32 | 'previous':true, 33 | 'disabled':this.props.page == 1 34 | }); 35 | var nextClass = cx({ 36 | 'next':true, 37 | 'disabled':this.props.page == this.props.pages 38 | });*/ 39 | 40 | return( 41 | 52 | ) 53 | } 54 | }); 55 | 56 | module.exports = Pager; -------------------------------------------------------------------------------- /app/templates/signup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 注册UNICORN 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 | 18 |
19 |
20 |

欢迎注册

21 | 22 | 23 | 24 | 25 |
26 |
27 | 38 | 39 | -------------------------------------------------------------------------------- /app/static/js/components/Header.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ReactEcharts = require('echarts-for-react'); 3 | var RadarChart = require('./RadarChart'); 4 | var LineChart = require('./LineChart'); 5 | var Footer = require('./Footer'); 6 | var Link = require('react-router').Link; 7 | 8 | var Header = React.createClass({ 9 | componentDidMount: function(){ 10 | $('.navbar-toggle').collapse(); 11 | console.log(this.props.children) 12 | $('li').on('click',function(e){ 13 | //console.log(e.target); 14 | $('.active').removeClass('active'); 15 | /*document.getElementsByTagName('li').map(function(single){ 16 | single.className = ""; 17 | });*/ 18 | $(this).addClass('active'); 19 | }); 20 | }, 21 | render: function(){ 22 | return( 23 |
24 | 49 | { this.props.children || } 50 |
51 |
52 | ) 53 | } 54 | 55 | }); 56 | 57 | module.exports = Header; -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | less = require('gulp-less'), 3 | reactify = require('reactify'), 4 | browserify = require('browserify'), 5 | babelify = require('babelify'), 6 | watchify = require('watchify'); 7 | var source = require('vinyl-source-stream'); 8 | 9 | gulp.task('less', function(){ 10 | gulp.src('./app/static/less/index.less') //该任务针对的文件 11 | .pipe(less()) //该任务调用的模块 12 | .pipe(gulp.dest('./app/static/css')) //将会在src/css下生成index.css 13 | }); 14 | 15 | gulp.task('react', function(){ 16 | var bundler = browserify({ 17 | entries: ['./app/static/js/lib/index.js'], // Only need initial file, browserify finds the deps 18 | transform: [reactify], // We want to convert JSX to normal javascript 19 | debug: true, // Gives us sourcemapping 20 | cache: {}, packageCache: {}, fullPaths: false // Requirement of watchify 21 | }); 22 | var watcher = watchify(bundler); 23 | 24 | return watcher 25 | .on('update', function () { // When any files update 26 | var updateStart = Date.now(); 27 | console.log('Updating react!'); 28 | watcher.bundle() // Create new bundle that uses the cache for high performance 29 | .pipe(source('bundle.js')) 30 | // This is where you add uglifying etc. 31 | .pipe(gulp.dest('./app/static/js/build')); 32 | console.log('Updated react!', (Date.now() - updateStart) + 'ms'); 33 | }).on('error', function(err){ 34 | // print the error (can replace with gulp-util) 35 | console.log(err.message); 36 | // end this stream 37 | this.emit('end'); 38 | }) 39 | .bundle() // Create the initial bundle when starting the task 40 | .pipe(source('bundle.js')) 41 | .pipe(gulp.dest('./app/static/js/build')); 42 | 43 | /*return browserify('./app/static/js/lib/index.js') 44 | .transform(reactify) 45 | .bundle() 46 | .pipe(source('bundle.js')) 47 | .pipe(gulp.dest('./app/static/js/build'));*/ 48 | /*var bundler = browserify({ 49 | entries: './app/static/js/lib/index.js', 50 | debug: true, 51 | transform: [reactify] 52 | }); 53 | var watcher = watchify(bundler); 54 | 55 | return watcher 56 | .on('update', function(){ 57 | watcher.bundle().pipe(source('bundle.js')) 58 | .pipe(gulp.dest('./app/static/js/build')); 59 | }) 60 | .bundle() 61 | .pipe(source('bundle.js')) 62 | .pipe(gulp.dest('./app/static/js/build'));*/ 63 | }); 64 | 65 | gulp.task('default',['less', 'react']); //定义默认任务 elseTask为其他任务,该示例没有定义elseTask任务 -------------------------------------------------------------------------------- /app/models.py: -------------------------------------------------------------------------------- 1 | from app import app 2 | from app import db 3 | from datetime import datetime 4 | from flask_login import UserMixin 5 | 6 | 7 | class Info(db.Document): 8 | name = db.StringField() 9 | webName = db.StringField() 10 | hitNum = db.IntField() 11 | commentNum = db.IntField() 12 | collectionNum = db.IntField() 13 | caiNum = db.IntField() 14 | likeNum = db.IntField() 15 | url = db.StringField() 16 | crawlTime = db.StringField() 17 | 18 | def to_json(self): 19 | return { 20 | 'cartoonName': self.name, 21 | 'webName': self.webName, 22 | 'hitNum': self.hitNum, 23 | 'commentNum': self.commentNum, 24 | 'caiNum': self.caiNum, 25 | 'likeNum': self.likeNum, 26 | 'collectionNum': self.collectionNum, 27 | 'crawlTime': self.crawlTime 28 | } 29 | class Info_increment(db.Document): 30 | name = db.StringField() 31 | webName = db.StringField() 32 | hitNum = db.IntField() 33 | commentNum = db.IntField() 34 | collectionNum = db.IntField() 35 | caiNum = db.IntField() 36 | likeNum = db.IntField() 37 | url = db.StringField() 38 | crawlTime = db.StringField() 39 | 40 | def to_json(self): 41 | return { 42 | 'cartoonName': self.name, 43 | 'webName': self.webName, 44 | 'hitNum': self.hitNum, 45 | 'commentNum': self.commentNum, 46 | 'caiNum': self.caiNum, 47 | 'likeNum': self.likeNum, 48 | 'collectionNum': self.collectionNum, 49 | 'crawlTime': self.crawlTime 50 | } 51 | 52 | class Comment(db.Document): 53 | name = db.StringField() 54 | webName = db.StringField() 55 | author = db.StringField() 56 | avatar = db.StringField() 57 | sex = db.IntField()#暂时用,以后会去掉 58 | content = db.StringField() 59 | commentTime = db.StringField() 60 | crawlTime = db.StringField() 61 | def to_json(self): 62 | return { 63 | 'cartoonName': self.name, 64 | 'webName': self.webName, 65 | 'author': self.author, 66 | 'content': self.content, 67 | 'commentTime': self.commentTime, 68 | 'sex': self.sex, #暂时用,以后会去掉 69 | 'avatar': self.avatar, 70 | 'crawlTime': self.crawlTime 71 | } 72 | 73 | class User(db.Document, UserMixin): 74 | username = db.StringField(unique=True) 75 | password = db.StringField() 76 | #active = db.BooleanField(default=True) 77 | def get_id(self): 78 | return self.username 79 | -------------------------------------------------------------------------------- /app/static/js/components/CommentList.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Pager = require('./Pager'); 3 | 4 | var CommentList = React.createClass({ 5 | getInitialState : function(){ 6 | return { 7 | webName: "All", 8 | cartoonName: "All", 9 | page: 1, 10 | pages: 1, 11 | comments: [],//temp_info, 12 | } 13 | }, 14 | componentWillMount: function(){ 15 | this.listComment(1); 16 | }, 17 | listComment : function(page, webName, cartoonName){ 18 | var that = this; 19 | var stateChange = 0; 20 | if (arguments.length == 1) { 21 | var data = { 22 | page: page, 23 | webName: this.state.webName, 24 | cartoonName: this.state.cartoonName, 25 | }; 26 | } else { 27 | var data = { 28 | page: page, 29 | webName: webName, 30 | cartoonName: cartoonName, 31 | }; 32 | stateChange = 1; 33 | } 34 | $.ajax({ 35 | type: 'post', 36 | url: '/api/info/comment', 37 | data: data 38 | }).done(function (resp) { 39 | if(resp.status == "success"){ 40 | var pager = resp.pager; 41 | if (stateChange == 1) { 42 | that.setState({ 43 | comments: pager.comments, 44 | page: pager.page, 45 | pages: pager.pages, 46 | webName: webName, 47 | cartoonName: cartoonName, 48 | }); 49 | } else { 50 | that.setState({ 51 | comments: pager.comments, 52 | page: pager.page, 53 | pages: pager.pages 54 | }); 55 | } 56 | } 57 | }.bind(this)); 58 | }, 59 | componentDidMount: function(){ 60 | var that = this; 61 | $("#cartoon-name").change(function(evt){ 62 | var webName = that.state.webName; 63 | that.listComment(1, webName, evt.target.value); 64 | }); 65 | $("#website").change(function(evt){ 66 | var cartoonName = that.state.cartoonName; 67 | that.listComment(1, evt.target.value, cartoonName); 68 | }); 69 | }, 70 | render: function(){ 71 | var pager_props = { 72 | page : this.state.page, 73 | pages : this.state.pages, 74 | listComment : this.listComment, 75 | }; 76 | var comments = this.state.comments.map(function(comment){ 77 | return( 78 |
79 |

《{comment.cartoonName}》   80 | 来源:{comment.webName}({comment.commentTime})    昵称:{comment.author} 81 |

82 |

  {comment.content}

83 |
84 |
85 | ); 86 | }); 87 | return( 88 |
89 |
90 |
91 | 选择作品: 92 | 101 |
102 |
103 | 选择网站: 104 | 113 |
114 |
115 |
116 | {comments} 117 | 118 |
119 |
120 | ) 121 | } 122 | 123 | }); 124 | 125 | module.exports = CommentList; -------------------------------------------------------------------------------- /app/static/js/components/CompareChart.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var React = require('react'); 3 | //var CompareChart = require('./CompareChart') 4 | var ReactEcharts = require('echarts-for-react').default; 5 | 6 | var CompareChart = React.createClass({ 7 | getInitialState : function(){ 8 | /*var temp_info = $.ajax({ 9 | type: "post", 10 | url: "/info/line", 11 | data: {webName:"腾讯", cartoonName:"镖人"} 12 | });*/ 13 | return { 14 | webName: "腾讯", 15 | kind: "点击数", 16 | info: [],//temp_info, 17 | } 18 | }, 19 | getOption: function() { 20 | var seriesData = []; 21 | var dates = []; 22 | var legendData = []; 23 | //console.log(this.state.info.length); 24 | if (this.state.info.length != 0) { 25 | //console.log(this.state.info); 26 | this.state.info.map(function(single){ 27 | for (var key in single) { 28 | if (key != 'date') { 29 | legendData.push(key); 30 | seriesData.push({ 31 | name: key, 32 | type: 'line', 33 | data: single[key], 34 | }); 35 | } else { 36 | if (dates.length == 0) { 37 | dates = single[key]; 38 | } 39 | } 40 | } 41 | }); 42 | } 43 | /*console.log(dates); 44 | console.log(legendData); 45 | console.log(seriesData);*/ 46 | var option = { 47 | title: { 48 | //text: this.state.webName + '《' + this.state.cartoonName + '》数据图', 49 | }, 50 | tooltip: { 51 | trigger: 'axis' 52 | }, 53 | legend: { 54 | data: legendData, 55 | }, 56 | grid: { 57 | //left: '3%', 58 | //right: '4%', 59 | bottom: '15%', 60 | containLabel: true 61 | }, 62 | toolbox: { 63 | feature: { 64 | saveAsImage: {} 65 | } 66 | }, 67 | dataZoom: [{ 68 | textStyle: { 69 | color: '#8392A5' 70 | }, 71 | handleIcon: 'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z', 72 | handleSize: '80%', 73 | dataBackground: { 74 | areaStyle: { 75 | color: '#8392A5' 76 | }, 77 | lineStyle: { 78 | opacity: 0.8, 79 | color: '#8392A5' 80 | } 81 | }, 82 | handleStyle: { 83 | color: '#fff', 84 | shadowBlur: 3, 85 | shadowColor: 'rgba(0, 0, 0, 0.6)', 86 | shadowOffsetX: 2, 87 | shadowOffsetY: 2, 88 | } 89 | }, { 90 | type: 'inside' 91 | }], 92 | xAxis: { 93 | type: 'category', 94 | data: dates, 95 | axisLine: { lineStyle: { color: '#8392A5' } } 96 | }, 97 | yAxis: { 98 | type: 'value' //不可以是category 99 | }, 100 | series: seriesData 101 | }; 102 | return option; 103 | }, 104 | componentWillMount: function(){ 105 | $.ajax({ 106 | type: "post", 107 | url: "/api/info/compare", 108 | data: {webName:this.state.webName, kind:this.state.kind} 109 | }).done(function (resp) { 110 | //console.log(resp.data); 111 | if(resp.status == "success"){ 112 | this.setState({ 113 | info: resp.data, 114 | }); 115 | } 116 | }.bind(this)); 117 | }, 118 | componentDidMount: function(){ 119 | var that = this; 120 | $("#kind").change(function(evt){ 121 | //console.log(evt.target.value); 122 | $.ajax({ 123 | type: "post", 124 | url: "/api/info/compare", 125 | data: {webName:that.state.webName, kind:evt.target.value} 126 | }).done(function (resp) { 127 | 128 | if(resp.status == "success"){ 129 | that.setState({ 130 | kind: evt.target.value, 131 | info: resp.data, 132 | }); 133 | } 134 | }.bind(this)); 135 | }); 136 | $("#website").change(function(evt){ 137 | //console.log(evt.target.value); 138 | $.ajax({ 139 | type: "post", 140 | url: "/api/info/compare", 141 | data: {webName:evt.target.value, kind:that.state.kind} 142 | }).done(function (resp) { 143 | if(resp.status == "success"){ 144 | that.setState({ 145 | webName: evt.target.value, 146 | info: resp.data, 147 | }); 148 | } 149 | }.bind(this)); 150 | }); 151 | }, 152 | render: function() { 153 | return ( 154 |
155 |
156 |
157 | 选择网站: 158 | 166 |
167 |
168 | 选择对比项: 169 | 176 |
177 |
178 |
179 |

{this.state.webName}[{this.state.kind}]数据对比图

180 | 181 |

注:数据为-1代表此网站不存在该项数据

182 |
183 |
184 | ); 185 | } 186 | }); 187 | 188 | module.exports = CompareChart; -------------------------------------------------------------------------------- /app/static/js/components/RadarChart.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var React = require('react'); 4 | var ReactEcharts = require('echarts-for-react').default; 5 | 6 | var RadarChart = React.createClass({ 7 | getInitialState : function(){ 8 | return { 9 | dataType: "总量", 10 | dateTime: this.getYesterdayDate(), 11 | webName: "腾讯", 12 | info: [], 13 | } 14 | }, 15 | getYesterdayDate: function(){ 16 | var now = new Date(); 17 | var year = now.getFullYear(); //年 18 | var month = now.getMonth() + 1; //月 19 | var day = now.getDate(); //日 20 | var clock = year + "-"; 21 | 22 | if(month < 10) 23 | clock += "0"; 24 | clock += month + "-"; 25 | if(day < 10) 26 | clock += "0"; 27 | clock += day; 28 | 29 | return clock; 30 | }, 31 | getOption: function() { 32 | var seriesData = []; 33 | var legendData = []; 34 | if (this.state.info.length != 0) { 35 | this.state.info.map(function(single){ 36 | console.log(single); 37 | var numIndex = []; 38 | numIndex.push(single.hitNum); 39 | numIndex.push(single.commentNum); 40 | numIndex.push(single.collectionNum); 41 | numIndex.push(single.caiNum); 42 | numIndex.push(single.likeNum); 43 | seriesData.push({value: numIndex, name: single.name}); 44 | legendData.push(single.name); 45 | }); 46 | } 47 | console.log(seriesData); 48 | console.log(legendData); 49 | var option = { 50 | title: { 51 | //text: this.state.webName + '漫画数据图'+'\n(截止'+ this.state.dateTime +'数据)', 52 | top: 1, 53 | left: 'left', 54 | }, 55 | tooltip: {}, 56 | legend: { 57 | //padding: 5, 58 | //top: 20, 59 | bottom: 'bottom', 60 | data: legendData//['镖人', '白狼汐', ]//"BRAVE", "森林人间塾", "漫画社X的复活" 61 | }, 62 | radar: { 63 | //shape: 'circle', 64 | indicator: [ 65 | { name: '点击量', }, 66 | { name: '评论数', }, 67 | { name: '收藏数', }, 68 | { name: '踩数', }, 69 | { name: '喜欢数', }, 70 | ], 71 | //silent: true, 72 | }, 73 | series: [{ 74 | //name: '镖人 vs 白狼汐', 75 | type: 'radar', 76 | data : seriesData 77 | /*[ 78 | { 79 | value : [4300, 10000, 28000, -1, 50000], 80 | name : '镖人' 81 | }, 82 | { 83 | value : [5000, 14000, 28000, 31000, 42000], 84 | name : '白狼汐' 85 | } 86 | 87 | ]*/ 88 | }] 89 | }; 90 | return option; 91 | }, 92 | componentWillMount: function(){ 93 | $.ajax({ 94 | type: "post", 95 | url: "/api/info/radar", 96 | data: {webName:this.state.webName, dateTime:this.state.dateTime, dataType:this.state.dataType} 97 | }).done(function (resp) { 98 | console.log(resp.data); 99 | if(resp.status == "success"){ 100 | this.setState({ 101 | info: resp.data, 102 | }); 103 | } 104 | }.bind(this)); 105 | }, 106 | componentDidMount: function(){ 107 | var that = this; 108 | $("#datetime").datetimepicker({ 109 | format: 'yyyy-mm-dd', 110 | autoclose: true, 111 | minView: 'month', 112 | //language: 'zh-CN', 113 | todayBtn: true, 114 | maxView: 'year', 115 | }); 116 | $("#datetime").change(function(evt){ 117 | console.log(evt.target.value); 118 | $.ajax({ 119 | type: "post", 120 | url: "/api/info/radar", 121 | data: {webName:that.state.webName, dateTime:evt.target.value, dataType:that.state.dataType} 122 | }).done(function (resp) { 123 | var data = resp.data; 124 | console.log(data); 125 | if(resp.status == "success"){ 126 | that.setState({ 127 | dateTime: evt.target.value, 128 | info: resp.data, 129 | }); 130 | } 131 | }.bind(this)); 132 | }); 133 | $("#data-type").change(function(evt){ 134 | console.log(that.state.dateTime); 135 | console.log(evt.target.value); 136 | $.ajax({ 137 | type: "post", 138 | url: "/api/info/radar", 139 | data: {webName:that.state.webName, dateTime:that.state.dateTime, dataType:evt.target.value} 140 | }).done(function (resp) { 141 | if(resp.status == "success"){ 142 | that.setState({ 143 | dataType: evt.target.value, 144 | info: resp.data, 145 | }); 146 | } 147 | }.bind(this)); 148 | }); 149 | $("#website").change(function(evt){ 150 | console.log(that.state.dateTime); 151 | console.log(evt.target.value); 152 | $.ajax({ 153 | type: "post", 154 | url: "/api/info/radar", 155 | data: {webName:evt.target.value, dateTime:that.state.dateTime, dataType:that.state.dataType} 156 | }).done(function (resp) { 157 | if(resp.status == "success"){ 158 | that.setState({ 159 | webName: evt.target.value, 160 | info: resp.data, 161 | }); 162 | } 163 | }.bind(this)); 164 | }); 165 | }, 166 | render: function() { 167 | return ( 168 |
169 |
170 |
171 | 选择日期: 172 | 173 |
174 |
175 | 选择网站: 176 | 184 |
185 |
186 | 选择数据类型: 187 | 191 |
192 |
193 |
194 |

{this.state.webName}漫画数据图(截止{this.state.dateTime}数据)

195 | 196 |

注:数据为-1代表此网站不存在该项数据

197 |
198 |
199 | ); 200 | } 201 | }); 202 | 203 | module.exports = RadarChart; -------------------------------------------------------------------------------- /app/views.py: -------------------------------------------------------------------------------- 1 | from flask import render_template, request, jsonify, redirect, url_for, flash 2 | from app import app 3 | from app import db, login_manager 4 | import flask_login as flask_login 5 | from .models import Info,Comment,User,Info_increment 6 | import json 7 | import logging 8 | log_file = "./basic_logger.log" 9 | logging.basicConfig(filename = log_file, level = logging.DEBUG) 10 | 11 | 12 | @login_manager.user_loader 13 | def load_user(id): 14 | logging.warning(id) 15 | if id is None: 16 | redirect('/login') 17 | user = User() 18 | user.get_id() 19 | logging.warning(user.__dict__) 20 | return user 21 | 22 | 23 | @login_manager.unauthorized_handler 24 | def unauthorized_callback(): 25 | return redirect('/login') 26 | 27 | @app.route('/') 28 | @app.route('/login', methods=['GET', 'POST']) 29 | def login(): 30 | if request.method == 'GET': 31 | return render_template('login.html') 32 | 33 | username = request.form['username'] 34 | user = json.loads(User.objects(username=username).to_json()) 35 | if len(user) == 0: 36 | return 'Username Error!' 37 | user = user[0] 38 | logging.error(type(user)) 39 | if request.form['password'] == user['password']: 40 | user = User() 41 | user.username = username 42 | flask_login.login_user(user)#这句之后会自动执load_user(user_loader)的方法,找到指定的用户 43 | return redirect('/data/radar') 44 | 45 | return 'Password Error' 46 | 47 | @app.route("/signup", methods=["GET","POST"]) 48 | def signup(): 49 | if request.method == 'GET': 50 | return render_template('signup.html') 51 | 52 | username = request.form['username'] 53 | password = request.form['password'] 54 | user = User.objects(username=username) 55 | if len(user) == 0: 56 | user = User(username=username, password=password) 57 | if user.save(): 58 | flash("signup success!") 59 | return redirect('/login') 60 | flash("用户名已被注册") 61 | return redirect('/signup') 62 | 63 | @app.route("/logout") 64 | @flask_login.login_required 65 | def logout(): 66 | flask_login.logout_user() 67 | flash("Logged out.") 68 | return redirect('/login') 69 | 70 | @app.route('/data/radar') 71 | @app.route('/data/line') 72 | @app.route('/data/compare') 73 | @app.route('/data/comment') 74 | @flask_login.login_required 75 | def index(): 76 | return render_template('index.html') 77 | 78 | ''' 79 | url: "/api/info/radar" 80 | type: post 81 | 功能: 通过日期和网站名得到数据库中的各个作品的各项(累计)数据,作为填充雷达图的数据 82 | input: {dateTime,webName} 83 | output: 84 | { 85 | status:"success", 86 | data:[object,object,object], #object详见models中的Info数据结构 87 | } 88 | ''' 89 | 90 | 91 | @app.route("/api/info/radar", methods=["POST"]) 92 | def get_data_by_datetime_webname(): 93 | dateTime = request.form.get("dateTime") 94 | webName = request.form.get("webName") 95 | dataType = request.form.get("dataType") 96 | if dataType == "总量": 97 | data = Info.objects(crawlTime__contains=dateTime,webName__contains=webName) 98 | else: 99 | data = Info_increment.objects(crawlTime__contains=dateTime,webName__contains=webName) 100 | 101 | return jsonify(status="success", data=data) 102 | 103 | 104 | ''' 105 | url: "/api/info/line" 106 | type: post 107 | 功能: 通过漫画名和网站名查找数据库中的某个作品各个时间点的各项(累计)数据 108 | input: {cartoonName,webName} 109 | output: 110 | { 111 | status:"success", 112 | data:[object,object,object], #object详见models中的Info数据结构 113 | } 114 | ''' 115 | 116 | 117 | @app.route("/api/info/line", methods=["POST"]) 118 | def get_data_by_cartoonname_webname(): 119 | cartoonName = request.form.get("cartoonName") 120 | webName = request.form.get("webName") 121 | data = Info.objects(name__contains=cartoonName,webName__contains=webName).order_by('crawlTime') 122 | 123 | return jsonify(status="success", data=data) 124 | 125 | 126 | ''' 127 | url: "/api/info/compare" 128 | type: post 129 | 功能: 通过类型名(点击数、评论数、收藏数、赞数、踩数)和网站名 130 | 查找数据库中的各个作品每个时间点的某项(累计)数据 131 | input: {kind,webName} 132 | output: 133 | { 134 | status: "success", 135 | data: 136 | [ 137 | { 138 | "镖人": [1,2], 139 | "date":["2016-07-04 22:00:00","2016-07-04 22:05:22"] 140 | }, 141 | { 142 | "brave": [3,56], 143 | "date":["2016-07-04 22:00:00","2016-07-04 22:05:22"] 144 | } 145 | ] 146 | } 147 | ''' 148 | 149 | 150 | @app.route("/api/info/compare", methods=["POST"]) 151 | def get_data_by_webname_kind(): 152 | webName = request.form.get("webName") 153 | kind = request.form.get("kind") 154 | rawData = list(Info.objects(webName__contains=webName).order_by('crawlTime')) 155 | #仿照m.to_json() for m in paginator.items试试,或者把数据放到前端处理 156 | if kind == "点击数": 157 | data = get_data_by_kind(rawData,"hitNum") 158 | elif kind == "评论数": 159 | data = get_data_by_kind(rawData,"commentNum") 160 | elif kind == "收藏数": 161 | data = get_data_by_kind(rawData,"collectionNum") 162 | elif kind == "赞数": 163 | data = get_data_by_kind(rawData,"likeNum") 164 | else: 165 | data = get_data_by_kind(rawData,"caiNum") 166 | return jsonify(status="success", data=data) 167 | 168 | ''' 169 | 功能: 通过类型名(hitNum、commentNum、collectionNum、likeNum、caiNum)和数据库中原始数据 170 | 把数据重新存储到cartoonList的结构中 171 | input: {rawData,kind} 172 | output: 173 | [ 174 | { 175 | "镖人": [1,2], 176 | "date":["2016-07-04 22:00:00","2016-07-04 22:05:22"] 177 | }, 178 | { 179 | "brave": [3,56], 180 | "date":["2016-07-04 22:00:00","2016-07-04 22:05:22"] 181 | } 182 | ] 183 | ''' 184 | def get_data_by_kind(rawData, kind): 185 | 186 | #把数据存储为cartoonList = [{"镖人":[1,2],"date":["2016-07-04 22:00:00","2016-07-04 22:05:22"]},{"brave":[3,56],"date":["2016-07-04 22:00:00","2016-07-04 22:05:22"]}]的形式 187 | 188 | cartoonList = [] 189 | for single in rawData: 190 | #logging.error(print(single['name'])) 191 | hasSaved = 0; 192 | for cartoon in cartoonList: 193 | if single['name'] in cartoon: 194 | cartoon[single['name']].append(single[kind]) 195 | cartoon['date'].append(single['crawlTime'].split( )[0]) 196 | hasSaved = 1 197 | break 198 | if hasSaved == 0: 199 | d = dict() 200 | kindNum = [single[kind]] 201 | date = [single['crawlTime'].split( )[0]] 202 | d[single['name']] = kindNum 203 | d['date'] = date 204 | cartoonList.append(d) 205 | #logging.info(print(cartoonList)) 206 | return cartoonList 207 | 208 | 209 | 210 | ''' 211 | url: "/api/info/comment" 212 | type: post 213 | 功能: 通过页码、网站名和作品名查找数据库中对应的评论内容 214 | input: {page,webName,cartoonName} 215 | output: 216 | { 217 | status: "success", 218 | pager: 219 | { 220 | 'page': 1, 221 | 'pages': 2, 222 | 'comments': [object,object,object] #object详见models中的Comment数据结构 223 | } 224 | } 225 | ''' 226 | @app.route("/api/info/comment", methods=["POST"]) 227 | def list_comment(): 228 | page = request.form.get("page") 229 | webName = request.form.get("webName") 230 | cartoonName = request.form.get("cartoonName") 231 | if cartoonName == 'All' and webName == "All": 232 | paginator = Comment.objects().order_by('-commentTime').paginate(page=int(page), per_page=20) 233 | elif cartoonName == 'All' and webName != 'All': 234 | paginator = Comment.objects(webName__contains=webName).order_by('-commentTime').paginate(page=int(page), per_page=20) 235 | elif cartoonName != 'All' and webName == 'All': 236 | paginator = Comment.objects(name__contains=cartoonName).order_by('-commentTime').paginate(page=int(page), per_page=20) 237 | else: 238 | paginator = Comment.objects(webName__contains=webName, name__contains=cartoonName).order_by('-commentTime').paginate(page=int(page), per_page=20) 239 | pager = { 240 | 'page': paginator.page, 241 | 'pages': paginator.pages, 242 | 'comments': [m.to_json() for m in paginator.items] 243 | } 244 | return jsonify(status="success", pager=pager) -------------------------------------------------------------------------------- /app/static/js/components/LineChart.js: -------------------------------------------------------------------------------- 1 | //"use strict"; 2 | var React = require('react'); 3 | var CompareChart = require('./CompareChart'); 4 | var ReactEcharts = require('echarts-for-react').default; 5 | 6 | var LineChart = React.createClass({ 7 | getInitialState : function(){ 8 | return { 9 | cartoonName: "镖人", 10 | webName: "腾讯", 11 | info: [],//temp_info, 12 | } 13 | }, 14 | componentWillMount: function(){ 15 | $.ajax({ 16 | type: "post", 17 | url: "/api/info/line", 18 | data: {webName:this.state.webName, cartoonName:this.state.cartoonName} 19 | }).done(function (resp) { 20 | if(resp.status == "success"){ 21 | //console.log(resp.data); 22 | /*this.state.info = resp.data; 23 | this.forceUpdate();*/ 24 | this.setState({ 25 | info: resp.data 26 | }); 27 | } 28 | }.bind(this)); 29 | }, 30 | getOption: function() { 31 | var seriesData = []; 32 | var dates = []; 33 | var legendData = []; 34 | //console.log(this.state.info); 35 | if (this.state.info.length !== 0) { 36 | //console.log(this.state.info); 37 | var hitNum = []; 38 | var commentNum = []; 39 | var likeNum = []; 40 | var collectionNum = []; 41 | var caiNum = []; 42 | /*this.state.info.done(function(data){ 43 | return data.map 44 | })*/ 45 | this.state.info.map(function(single){ 46 | dates.push(single.crawlTime.split(' ')[0]); 47 | hitNum.push(single.hitNum); 48 | commentNum.push(single.commentNum); 49 | likeNum.push(single.likeNum); 50 | collectionNum.push(single.collectionNum); 51 | caiNum.push(single.caiNum); 52 | }); 53 | seriesData.push( 54 | { 55 | name: '点击数', 56 | type: 'line', 57 | data: hitNum, 58 | }, 59 | { 60 | name: '评论数', 61 | type: 'line', 62 | data: commentNum, 63 | }, 64 | { 65 | name:'收藏数', 66 | type:'line', 67 | data: collectionNum, 68 | }, 69 | { 70 | name:'赞数', 71 | type:'line', 72 | data: likeNum, 73 | }, 74 | { 75 | name:'踩数', 76 | type:'line', 77 | data: caiNum, 78 | }); 79 | } 80 | // console.log(seriesData); 81 | var option = { 82 | title: { 83 | //text: this.state.webName + '《' + this.state.cartoonName + '》数据图', 84 | }, 85 | tooltip: { 86 | trigger: 'axis' 87 | }, 88 | legend: { 89 | data:['点击数','评论数','收藏数','赞数','踩数'] 90 | }, 91 | grid: { 92 | //left: '3%', 93 | //right: '4%', 94 | bottom: '15%', 95 | containLabel: true 96 | }, 97 | toolbox: { 98 | feature: { 99 | saveAsImage: {} 100 | } 101 | }, 102 | dataZoom: [{ 103 | textStyle: { 104 | color: '#8392A5' 105 | }, 106 | handleIcon: 'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z', 107 | handleSize: '80%', 108 | dataBackground: { 109 | areaStyle: { 110 | color: '#8392A5' 111 | }, 112 | lineStyle: { 113 | opacity: 0.8, 114 | color: '#8392A5' 115 | } 116 | }, 117 | handleStyle: { 118 | color: '#fff', 119 | shadowBlur: 3, 120 | shadowColor: 'rgba(0, 0, 0, 0.6)', 121 | shadowOffsetX: 2, 122 | shadowOffsetY: 2, 123 | } 124 | }, { 125 | type: 'inside' 126 | }], 127 | xAxis: { 128 | type: 'category', 129 | data: dates, 130 | axisLine: { lineStyle: { color: '#8392A5' } } 131 | }, 132 | yAxis: { 133 | type: 'value' //不可以是category 134 | }, 135 | series: seriesData, 136 | }; 137 | //console.info(option); 138 | return option; 139 | }, 140 | componentDidMount: function(){ 141 | var that = this; 142 | $("#cartoon-name").change(function(evt){ 143 | //console.log(evt.target.value); 144 | $.ajax({ 145 | type: "post", 146 | url: "/api/info/line", 147 | data: {webName:that.state.webName, cartoonName:evt.target.value} 148 | }).done(function (resp) { 149 | var data = resp.data; 150 | //console.log(data); 151 | if(resp.status == "success"){ 152 | if (data.length == 0){ 153 | alert("没有该项数据"); 154 | } 155 | that.setState({ 156 | cartoonName: evt.target.value, 157 | info: resp.data, 158 | }); 159 | 160 | } 161 | }.bind(this)); 162 | }); 163 | $("#website").change(function(evt){ 164 | //console.log(evt.target.value); 165 | $.ajax({ 166 | type: "post", 167 | url: "/api/info/line", 168 | data: {webName:evt.target.value, cartoonName:that.state.cartoonName} 169 | }).done(function (resp) { 170 | if(resp.status == "success"){ 171 | if (resp.data.length == 0){ 172 | alert("没有该项数据"); 173 | } 174 | that.setState({ 175 | webName: evt.target.value, 176 | info: resp.data, 177 | }); 178 | } 179 | }.bind(this)); 180 | }); 181 | }, 182 | render: function() { 183 | return ( 184 |
185 |
186 |
187 | 选择作品: 188 | 196 |
197 |
198 | 选择网站: 199 | 207 |
208 |
209 |
210 |

{this.state.webName}《{this.state.cartoonName}》数据图

211 | 212 |

注:数据为-1代表此网站不存在该项数据

213 |
214 |
215 | ); 216 | } 217 | }); 218 | 219 | module.exports = LineChart; -------------------------------------------------------------------------------- /app/static/css/bootstrap-datetimepicker.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Datetimepicker for Bootstrap 3 | * 4 | * Copyright 2012 Stefan Petre 5 | * Improvements by Andrew Rowls 6 | * Licensed under the Apache License v2.0 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | */ 10 | .datetimepicker { 11 | padding: 4px; 12 | margin-top: 1px; 13 | -webkit-border-radius: 4px; 14 | -moz-border-radius: 4px; 15 | border-radius: 4px; 16 | direction: ltr; 17 | } 18 | 19 | .datetimepicker-inline { 20 | width: 220px; 21 | } 22 | 23 | .datetimepicker.datetimepicker-rtl { 24 | direction: rtl; 25 | } 26 | 27 | .datetimepicker.datetimepicker-rtl table tr td span { 28 | float: right; 29 | } 30 | 31 | .datetimepicker-dropdown, .datetimepicker-dropdown-left { 32 | top: 0; 33 | left: 0; 34 | } 35 | 36 | [class*=" datetimepicker-dropdown"]:before { 37 | content: ''; 38 | display: inline-block; 39 | border-left: 7px solid transparent; 40 | border-right: 7px solid transparent; 41 | border-bottom: 7px solid #cccccc; 42 | border-bottom-color: rgba(0, 0, 0, 0.2); 43 | position: absolute; 44 | } 45 | 46 | [class*=" datetimepicker-dropdown"]:after { 47 | content: ''; 48 | display: inline-block; 49 | border-left: 6px solid transparent; 50 | border-right: 6px solid transparent; 51 | border-bottom: 6px solid #ffffff; 52 | position: absolute; 53 | } 54 | 55 | [class*=" datetimepicker-dropdown-top"]:before { 56 | content: ''; 57 | display: inline-block; 58 | border-left: 7px solid transparent; 59 | border-right: 7px solid transparent; 60 | border-top: 7px solid #cccccc; 61 | border-top-color: rgba(0, 0, 0, 0.2); 62 | border-bottom: 0; 63 | } 64 | 65 | [class*=" datetimepicker-dropdown-top"]:after { 66 | content: ''; 67 | display: inline-block; 68 | border-left: 6px solid transparent; 69 | border-right: 6px solid transparent; 70 | border-top: 6px solid #ffffff; 71 | border-bottom: 0; 72 | } 73 | 74 | .datetimepicker-dropdown-bottom-left:before { 75 | top: -7px; 76 | right: 6px; 77 | } 78 | 79 | .datetimepicker-dropdown-bottom-left:after { 80 | top: -6px; 81 | right: 7px; 82 | } 83 | 84 | .datetimepicker-dropdown-bottom-right:before { 85 | top: -7px; 86 | left: 6px; 87 | } 88 | 89 | .datetimepicker-dropdown-bottom-right:after { 90 | top: -6px; 91 | left: 7px; 92 | } 93 | 94 | .datetimepicker-dropdown-top-left:before { 95 | bottom: -7px; 96 | right: 6px; 97 | } 98 | 99 | .datetimepicker-dropdown-top-left:after { 100 | bottom: -6px; 101 | right: 7px; 102 | } 103 | 104 | .datetimepicker-dropdown-top-right:before { 105 | bottom: -7px; 106 | left: 6px; 107 | } 108 | 109 | .datetimepicker-dropdown-top-right:after { 110 | bottom: -6px; 111 | left: 7px; 112 | } 113 | 114 | .datetimepicker > div { 115 | display: none; 116 | } 117 | 118 | .datetimepicker.minutes div.datetimepicker-minutes { 119 | display: block; 120 | } 121 | 122 | .datetimepicker.hours div.datetimepicker-hours { 123 | display: block; 124 | } 125 | 126 | .datetimepicker.days div.datetimepicker-days { 127 | display: block; 128 | } 129 | 130 | .datetimepicker.months div.datetimepicker-months { 131 | display: block; 132 | } 133 | 134 | .datetimepicker.years div.datetimepicker-years { 135 | display: block; 136 | } 137 | 138 | .datetimepicker table { 139 | margin: 0; 140 | } 141 | 142 | .datetimepicker td, 143 | .datetimepicker th { 144 | text-align: center; 145 | width: 20px; 146 | height: 20px; 147 | -webkit-border-radius: 4px; 148 | -moz-border-radius: 4px; 149 | border-radius: 4px; 150 | border: none; 151 | } 152 | 153 | .table-striped .datetimepicker table tr td, 154 | .table-striped .datetimepicker table tr th { 155 | background-color: transparent; 156 | } 157 | 158 | .datetimepicker table tr td.minute:hover { 159 | background: #eeeeee; 160 | cursor: pointer; 161 | } 162 | 163 | .datetimepicker table tr td.hour:hover { 164 | background: #eeeeee; 165 | cursor: pointer; 166 | } 167 | 168 | .datetimepicker table tr td.day:hover { 169 | background: #eeeeee; 170 | cursor: pointer; 171 | } 172 | 173 | .datetimepicker table tr td.old, 174 | .datetimepicker table tr td.new { 175 | color: #999999; 176 | } 177 | 178 | .datetimepicker table tr td.disabled, 179 | .datetimepicker table tr td.disabled:hover { 180 | background: none; 181 | color: #999999; 182 | cursor: default; 183 | } 184 | 185 | .datetimepicker table tr td.today, 186 | .datetimepicker table tr td.today:hover, 187 | .datetimepicker table tr td.today.disabled, 188 | .datetimepicker table tr td.today.disabled:hover { 189 | background-color: #fde19a; 190 | background-image: -moz-linear-gradient(top, #fdd49a, #fdf59a); 191 | background-image: -ms-linear-gradient(top, #fdd49a, #fdf59a); 192 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a)); 193 | background-image: -webkit-linear-gradient(top, #fdd49a, #fdf59a); 194 | background-image: -o-linear-gradient(top, #fdd49a, #fdf59a); 195 | background-image: linear-gradient(top, #fdd49a, #fdf59a); 196 | background-repeat: repeat-x; 197 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0); 198 | border-color: #fdf59a #fdf59a #fbed50; 199 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 200 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 201 | } 202 | 203 | .datetimepicker table tr td.today:hover, 204 | .datetimepicker table tr td.today:hover:hover, 205 | .datetimepicker table tr td.today.disabled:hover, 206 | .datetimepicker table tr td.today.disabled:hover:hover, 207 | .datetimepicker table tr td.today:active, 208 | .datetimepicker table tr td.today:hover:active, 209 | .datetimepicker table tr td.today.disabled:active, 210 | .datetimepicker table tr td.today.disabled:hover:active, 211 | .datetimepicker table tr td.today.active, 212 | .datetimepicker table tr td.today:hover.active, 213 | .datetimepicker table tr td.today.disabled.active, 214 | .datetimepicker table tr td.today.disabled:hover.active, 215 | .datetimepicker table tr td.today.disabled, 216 | .datetimepicker table tr td.today:hover.disabled, 217 | .datetimepicker table tr td.today.disabled.disabled, 218 | .datetimepicker table tr td.today.disabled:hover.disabled, 219 | .datetimepicker table tr td.today[disabled], 220 | .datetimepicker table tr td.today:hover[disabled], 221 | .datetimepicker table tr td.today.disabled[disabled], 222 | .datetimepicker table tr td.today.disabled:hover[disabled] { 223 | background-color: #fdf59a; 224 | } 225 | 226 | .datetimepicker table tr td.today:active, 227 | .datetimepicker table tr td.today:hover:active, 228 | .datetimepicker table tr td.today.disabled:active, 229 | .datetimepicker table tr td.today.disabled:hover:active, 230 | .datetimepicker table tr td.today.active, 231 | .datetimepicker table tr td.today:hover.active, 232 | .datetimepicker table tr td.today.disabled.active, 233 | .datetimepicker table tr td.today.disabled:hover.active { 234 | background-color: #fbf069; 235 | } 236 | 237 | .datetimepicker table tr td.active, 238 | .datetimepicker table tr td.active:hover, 239 | .datetimepicker table tr td.active.disabled, 240 | .datetimepicker table tr td.active.disabled:hover { 241 | background-color: #006dcc; 242 | background-image: -moz-linear-gradient(top, #0088cc, #0044cc); 243 | background-image: -ms-linear-gradient(top, #0088cc, #0044cc); 244 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); 245 | background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); 246 | background-image: -o-linear-gradient(top, #0088cc, #0044cc); 247 | background-image: linear-gradient(top, #0088cc, #0044cc); 248 | background-repeat: repeat-x; 249 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); 250 | border-color: #0044cc #0044cc #002a80; 251 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 252 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 253 | color: #ffffff; 254 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 255 | } 256 | 257 | .datetimepicker table tr td.active:hover, 258 | .datetimepicker table tr td.active:hover:hover, 259 | .datetimepicker table tr td.active.disabled:hover, 260 | .datetimepicker table tr td.active.disabled:hover:hover, 261 | .datetimepicker table tr td.active:active, 262 | .datetimepicker table tr td.active:hover:active, 263 | .datetimepicker table tr td.active.disabled:active, 264 | .datetimepicker table tr td.active.disabled:hover:active, 265 | .datetimepicker table tr td.active.active, 266 | .datetimepicker table tr td.active:hover.active, 267 | .datetimepicker table tr td.active.disabled.active, 268 | .datetimepicker table tr td.active.disabled:hover.active, 269 | .datetimepicker table tr td.active.disabled, 270 | .datetimepicker table tr td.active:hover.disabled, 271 | .datetimepicker table tr td.active.disabled.disabled, 272 | .datetimepicker table tr td.active.disabled:hover.disabled, 273 | .datetimepicker table tr td.active[disabled], 274 | .datetimepicker table tr td.active:hover[disabled], 275 | .datetimepicker table tr td.active.disabled[disabled], 276 | .datetimepicker table tr td.active.disabled:hover[disabled] { 277 | background-color: #0044cc; 278 | } 279 | 280 | .datetimepicker table tr td.active:active, 281 | .datetimepicker table tr td.active:hover:active, 282 | .datetimepicker table tr td.active.disabled:active, 283 | .datetimepicker table tr td.active.disabled:hover:active, 284 | .datetimepicker table tr td.active.active, 285 | .datetimepicker table tr td.active:hover.active, 286 | .datetimepicker table tr td.active.disabled.active, 287 | .datetimepicker table tr td.active.disabled:hover.active { 288 | background-color: #003399; 289 | } 290 | 291 | .datetimepicker table tr td span { 292 | display: block; 293 | width: 23%; 294 | height: 54px; 295 | line-height: 54px; 296 | float: left; 297 | margin: 1%; 298 | cursor: pointer; 299 | -webkit-border-radius: 4px; 300 | -moz-border-radius: 4px; 301 | border-radius: 4px; 302 | } 303 | 304 | .datetimepicker .datetimepicker-hours span { 305 | height: 26px; 306 | line-height: 26px; 307 | } 308 | 309 | .datetimepicker .datetimepicker-hours table tr td span.hour_am, 310 | .datetimepicker .datetimepicker-hours table tr td span.hour_pm { 311 | width: 14.6%; 312 | } 313 | 314 | .datetimepicker .datetimepicker-hours fieldset legend, 315 | .datetimepicker .datetimepicker-minutes fieldset legend { 316 | margin-bottom: inherit; 317 | line-height: 30px; 318 | } 319 | 320 | .datetimepicker .datetimepicker-minutes span { 321 | height: 26px; 322 | line-height: 26px; 323 | } 324 | 325 | .datetimepicker table tr td span:hover { 326 | background: #eeeeee; 327 | } 328 | 329 | .datetimepicker table tr td span.disabled, 330 | .datetimepicker table tr td span.disabled:hover { 331 | background: none; 332 | color: #999999; 333 | cursor: default; 334 | } 335 | 336 | .datetimepicker table tr td span.active, 337 | .datetimepicker table tr td span.active:hover, 338 | .datetimepicker table tr td span.active.disabled, 339 | .datetimepicker table tr td span.active.disabled:hover { 340 | background-color: #006dcc; 341 | background-image: -moz-linear-gradient(top, #0088cc, #0044cc); 342 | background-image: -ms-linear-gradient(top, #0088cc, #0044cc); 343 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); 344 | background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); 345 | background-image: -o-linear-gradient(top, #0088cc, #0044cc); 346 | background-image: linear-gradient(top, #0088cc, #0044cc); 347 | background-repeat: repeat-x; 348 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); 349 | border-color: #0044cc #0044cc #002a80; 350 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 351 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 352 | color: #ffffff; 353 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 354 | } 355 | 356 | .datetimepicker table tr td span.active:hover, 357 | .datetimepicker table tr td span.active:hover:hover, 358 | .datetimepicker table tr td span.active.disabled:hover, 359 | .datetimepicker table tr td span.active.disabled:hover:hover, 360 | .datetimepicker table tr td span.active:active, 361 | .datetimepicker table tr td span.active:hover:active, 362 | .datetimepicker table tr td span.active.disabled:active, 363 | .datetimepicker table tr td span.active.disabled:hover:active, 364 | .datetimepicker table tr td span.active.active, 365 | .datetimepicker table tr td span.active:hover.active, 366 | .datetimepicker table tr td span.active.disabled.active, 367 | .datetimepicker table tr td span.active.disabled:hover.active, 368 | .datetimepicker table tr td span.active.disabled, 369 | .datetimepicker table tr td span.active:hover.disabled, 370 | .datetimepicker table tr td span.active.disabled.disabled, 371 | .datetimepicker table tr td span.active.disabled:hover.disabled, 372 | .datetimepicker table tr td span.active[disabled], 373 | .datetimepicker table tr td span.active:hover[disabled], 374 | .datetimepicker table tr td span.active.disabled[disabled], 375 | .datetimepicker table tr td span.active.disabled:hover[disabled] { 376 | background-color: #0044cc; 377 | } 378 | 379 | .datetimepicker table tr td span.active:active, 380 | .datetimepicker table tr td span.active:hover:active, 381 | .datetimepicker table tr td span.active.disabled:active, 382 | .datetimepicker table tr td span.active.disabled:hover:active, 383 | .datetimepicker table tr td span.active.active, 384 | .datetimepicker table tr td span.active:hover.active, 385 | .datetimepicker table tr td span.active.disabled.active, 386 | .datetimepicker table tr td span.active.disabled:hover.active { 387 | background-color: #003399; 388 | } 389 | 390 | .datetimepicker table tr td span.old { 391 | color: #999999; 392 | } 393 | 394 | .datetimepicker th.switch { 395 | width: 145px; 396 | } 397 | 398 | .datetimepicker th span.glyphicon { 399 | pointer-events: none; 400 | } 401 | 402 | .datetimepicker thead tr:first-child th, 403 | .datetimepicker tfoot th { 404 | cursor: pointer; 405 | } 406 | 407 | .datetimepicker thead tr:first-child th:hover, 408 | .datetimepicker tfoot th:hover { 409 | background: #eeeeee; 410 | } 411 | 412 | .input-append.date .add-on i, 413 | .input-prepend.date .add-on i, 414 | .input-group.date .input-group-addon span { 415 | cursor: pointer; 416 | width: 14px; 417 | height: 14px; 418 | } 419 | -------------------------------------------------------------------------------- /app/static/fonts/glyphicons-halflings-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /app/static/js/lib/bootstrap-datetimepicker.js: -------------------------------------------------------------------------------- 1 | /* ========================================================= 2 | * bootstrap-datetimepicker.js 3 | * ========================================================= 4 | * Copyright 2012 Stefan Petre 5 | * 6 | * Improvements by Andrew Rowls 7 | * Improvements by Sébastien Malot 8 | * Improvements by Yun Lai 9 | * Improvements by Kenneth Henderick 10 | * Improvements by CuGBabyBeaR 11 | * Improvements by Christian Vaas 12 | * 13 | * Project URL : http://www.malot.fr/bootstrap-datetimepicker 14 | * 15 | * Licensed under the Apache License, Version 2.0 (the "License"); 16 | * you may not use this file except in compliance with the License. 17 | * You may obtain a copy of the License at 18 | * 19 | * http://www.apache.org/licenses/LICENSE-2.0 20 | * 21 | * Unless required by applicable law or agreed to in writing, software 22 | * distributed under the License is distributed on an "AS IS" BASIS, 23 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | * See the License for the specific language governing permissions and 25 | * limitations under the License. 26 | * ========================================================= */ 27 | 28 | (function(factory){ 29 | if (typeof define === 'function' && define.amd) 30 | define(['jquery'], factory); 31 | else if (typeof exports === 'object') 32 | factory(require('jquery')); 33 | else 34 | factory(jQuery); 35 | 36 | }(function($, undefined){ 37 | 38 | // Add ECMA262-5 Array methods if not supported natively (IE8) 39 | if (!('indexOf' in Array.prototype)) { 40 | Array.prototype.indexOf = function (find, i) { 41 | if (i === undefined) i = 0; 42 | if (i < 0) i += this.length; 43 | if (i < 0) i = 0; 44 | for (var n = this.length; i < n; i++) { 45 | if (i in this && this[i] === find) { 46 | return i; 47 | } 48 | } 49 | return -1; 50 | } 51 | } 52 | 53 | function elementOrParentIsFixed (element) { 54 | var $element = $(element); 55 | var $checkElements = $element.add($element.parents()); 56 | var isFixed = false; 57 | $checkElements.each(function(){ 58 | if ($(this).css('position') === 'fixed') { 59 | isFixed = true; 60 | return false; 61 | } 62 | }); 63 | return isFixed; 64 | } 65 | 66 | function UTCDate() { 67 | return new Date(Date.UTC.apply(Date, arguments)); 68 | } 69 | 70 | function UTCToday() { 71 | var today = new Date(); 72 | return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate(), today.getUTCHours(), today.getUTCMinutes(), today.getUTCSeconds(), 0); 73 | } 74 | 75 | // Picker object 76 | var Datetimepicker = function (element, options) { 77 | var that = this; 78 | 79 | this.element = $(element); 80 | 81 | // add container for single page application 82 | // when page switch the datetimepicker div will be removed also. 83 | this.container = options.container || 'body'; 84 | 85 | this.language = options.language || this.element.data('date-language') || 'en'; 86 | this.language = this.language in dates ? this.language : this.language.split('-')[0]; // fr-CA fallback to fr 87 | this.language = this.language in dates ? this.language : 'en'; 88 | this.isRTL = dates[this.language].rtl || false; 89 | this.formatType = options.formatType || this.element.data('format-type') || 'standard'; 90 | this.format = DPGlobal.parseFormat(options.format || this.element.data('date-format') || dates[this.language].format || DPGlobal.getDefaultFormat(this.formatType, 'input'), this.formatType); 91 | this.isInline = false; 92 | this.isVisible = false; 93 | this.isInput = this.element.is('input'); 94 | this.fontAwesome = options.fontAwesome || this.element.data('font-awesome') || false; 95 | 96 | this.bootcssVer = options.bootcssVer || (this.isInput ? (this.element.is('.form-control') ? 3 : 2) : ( this.bootcssVer = this.element.is('.input-group') ? 3 : 2 )); 97 | 98 | this.component = this.element.is('.date') ? ( this.bootcssVer == 3 ? this.element.find('.input-group-addon .glyphicon-th, .input-group-addon .glyphicon-time, .input-group-addon .glyphicon-remove, .input-group-addon .glyphicon-calendar, .input-group-addon .fa-calendar, .input-group-addon .fa-clock-o').parent() : this.element.find('.add-on .icon-th, .add-on .icon-time, .add-on .icon-calendar, .add-on .fa-calendar, .add-on .fa-clock-o').parent()) : false; 99 | this.componentReset = this.element.is('.date') ? ( this.bootcssVer == 3 ? this.element.find('.input-group-addon .glyphicon-remove, .input-group-addon .fa-times').parent():this.element.find('.add-on .icon-remove, .add-on .fa-times').parent()) : false; 100 | this.hasInput = this.component && this.element.find('input').length; 101 | if (this.component && this.component.length === 0) { 102 | this.component = false; 103 | } 104 | this.linkField = options.linkField || this.element.data('link-field') || false; 105 | this.linkFormat = DPGlobal.parseFormat(options.linkFormat || this.element.data('link-format') || DPGlobal.getDefaultFormat(this.formatType, 'link'), this.formatType); 106 | this.minuteStep = options.minuteStep || this.element.data('minute-step') || 5; 107 | this.pickerPosition = options.pickerPosition || this.element.data('picker-position') || 'bottom-right'; 108 | this.showMeridian = options.showMeridian || this.element.data('show-meridian') || false; 109 | this.initialDate = options.initialDate || new Date(); 110 | this.zIndex = options.zIndex || this.element.data('z-index') || undefined; 111 | this.title = typeof options.title === 'undefined' ? false : options.title; 112 | 113 | this.icons = { 114 | leftArrow: this.fontAwesome ? 'fa-arrow-left' : (this.bootcssVer === 3 ? 'glyphicon-arrow-left' : 'icon-arrow-left'), 115 | rightArrow: this.fontAwesome ? 'fa-arrow-right' : (this.bootcssVer === 3 ? 'glyphicon-arrow-right' : 'icon-arrow-right') 116 | } 117 | this.icontype = this.fontAwesome ? 'fa' : 'glyphicon'; 118 | 119 | this._attachEvents(); 120 | 121 | this.clickedOutside = function (e) { 122 | // Clicked outside the datetimepicker, hide it 123 | if ($(e.target).closest('.datetimepicker').length === 0) { 124 | that.hide(); 125 | } 126 | } 127 | 128 | this.formatViewType = 'datetime'; 129 | if ('formatViewType' in options) { 130 | this.formatViewType = options.formatViewType; 131 | } else if ('formatViewType' in this.element.data()) { 132 | this.formatViewType = this.element.data('formatViewType'); 133 | } 134 | 135 | this.minView = 0; 136 | if ('minView' in options) { 137 | this.minView = options.minView; 138 | } else if ('minView' in this.element.data()) { 139 | this.minView = this.element.data('min-view'); 140 | } 141 | this.minView = DPGlobal.convertViewMode(this.minView); 142 | 143 | this.maxView = DPGlobal.modes.length - 1; 144 | if ('maxView' in options) { 145 | this.maxView = options.maxView; 146 | } else if ('maxView' in this.element.data()) { 147 | this.maxView = this.element.data('max-view'); 148 | } 149 | this.maxView = DPGlobal.convertViewMode(this.maxView); 150 | 151 | this.wheelViewModeNavigation = false; 152 | if ('wheelViewModeNavigation' in options) { 153 | this.wheelViewModeNavigation = options.wheelViewModeNavigation; 154 | } else if ('wheelViewModeNavigation' in this.element.data()) { 155 | this.wheelViewModeNavigation = this.element.data('view-mode-wheel-navigation'); 156 | } 157 | 158 | this.wheelViewModeNavigationInverseDirection = false; 159 | 160 | if ('wheelViewModeNavigationInverseDirection' in options) { 161 | this.wheelViewModeNavigationInverseDirection = options.wheelViewModeNavigationInverseDirection; 162 | } else if ('wheelViewModeNavigationInverseDirection' in this.element.data()) { 163 | this.wheelViewModeNavigationInverseDirection = this.element.data('view-mode-wheel-navigation-inverse-dir'); 164 | } 165 | 166 | this.wheelViewModeNavigationDelay = 100; 167 | if ('wheelViewModeNavigationDelay' in options) { 168 | this.wheelViewModeNavigationDelay = options.wheelViewModeNavigationDelay; 169 | } else if ('wheelViewModeNavigationDelay' in this.element.data()) { 170 | this.wheelViewModeNavigationDelay = this.element.data('view-mode-wheel-navigation-delay'); 171 | } 172 | 173 | this.startViewMode = 2; 174 | if ('startView' in options) { 175 | this.startViewMode = options.startView; 176 | } else if ('startView' in this.element.data()) { 177 | this.startViewMode = this.element.data('start-view'); 178 | } 179 | this.startViewMode = DPGlobal.convertViewMode(this.startViewMode); 180 | this.viewMode = this.startViewMode; 181 | 182 | this.viewSelect = this.minView; 183 | if ('viewSelect' in options) { 184 | this.viewSelect = options.viewSelect; 185 | } else if ('viewSelect' in this.element.data()) { 186 | this.viewSelect = this.element.data('view-select'); 187 | } 188 | this.viewSelect = DPGlobal.convertViewMode(this.viewSelect); 189 | 190 | this.forceParse = true; 191 | if ('forceParse' in options) { 192 | this.forceParse = options.forceParse; 193 | } else if ('dateForceParse' in this.element.data()) { 194 | this.forceParse = this.element.data('date-force-parse'); 195 | } 196 | var template = this.bootcssVer === 3 ? DPGlobal.templateV3 : DPGlobal.template; 197 | while (template.indexOf('{iconType}') !== -1) { 198 | template = template.replace('{iconType}', this.icontype); 199 | } 200 | while (template.indexOf('{leftArrow}') !== -1) { 201 | template = template.replace('{leftArrow}', this.icons.leftArrow); 202 | } 203 | while (template.indexOf('{rightArrow}') !== -1) { 204 | template = template.replace('{rightArrow}', this.icons.rightArrow); 205 | } 206 | this.picker = $(template) 207 | .appendTo(this.isInline ? this.element : this.container) // 'body') 208 | .on({ 209 | click: $.proxy(this.click, this), 210 | mousedown: $.proxy(this.mousedown, this) 211 | }); 212 | 213 | if (this.wheelViewModeNavigation) { 214 | if ($.fn.mousewheel) { 215 | this.picker.on({mousewheel: $.proxy(this.mousewheel, this)}); 216 | } else { 217 | console.log('Mouse Wheel event is not supported. Please include the jQuery Mouse Wheel plugin before enabling this option'); 218 | } 219 | } 220 | 221 | if (this.isInline) { 222 | this.picker.addClass('datetimepicker-inline'); 223 | } else { 224 | this.picker.addClass('datetimepicker-dropdown-' + this.pickerPosition + ' dropdown-menu'); 225 | } 226 | if (this.isRTL) { 227 | this.picker.addClass('datetimepicker-rtl'); 228 | var selector = this.bootcssVer === 3 ? '.prev span, .next span' : '.prev i, .next i'; 229 | this.picker.find(selector).toggleClass(this.icons.leftArrow + ' ' + this.icons.rightArrow); 230 | } 231 | 232 | $(document).on('mousedown', this.clickedOutside); 233 | 234 | this.autoclose = false; 235 | if ('autoclose' in options) { 236 | this.autoclose = options.autoclose; 237 | } else if ('dateAutoclose' in this.element.data()) { 238 | this.autoclose = this.element.data('date-autoclose'); 239 | } 240 | 241 | this.keyboardNavigation = true; 242 | if ('keyboardNavigation' in options) { 243 | this.keyboardNavigation = options.keyboardNavigation; 244 | } else if ('dateKeyboardNavigation' in this.element.data()) { 245 | this.keyboardNavigation = this.element.data('date-keyboard-navigation'); 246 | } 247 | 248 | this.todayBtn = (options.todayBtn || this.element.data('date-today-btn') || false); 249 | this.clearBtn = (options.clearBtn || this.element.data('date-clear-btn') || false); 250 | this.todayHighlight = (options.todayHighlight || this.element.data('date-today-highlight') || false); 251 | 252 | this.weekStart = ((options.weekStart || this.element.data('date-weekstart') || dates[this.language].weekStart || 0) % 7); 253 | this.weekEnd = ((this.weekStart + 6) % 7); 254 | this.startDate = -Infinity; 255 | this.endDate = Infinity; 256 | this.datesDisabled = []; 257 | this.daysOfWeekDisabled = []; 258 | this.setStartDate(options.startDate || this.element.data('date-startdate')); 259 | this.setEndDate(options.endDate || this.element.data('date-enddate')); 260 | this.setDatesDisabled(options.datesDisabled || this.element.data('date-dates-disabled')); 261 | this.setDaysOfWeekDisabled(options.daysOfWeekDisabled || this.element.data('date-days-of-week-disabled')); 262 | this.setMinutesDisabled(options.minutesDisabled || this.element.data('date-minute-disabled')); 263 | this.setHoursDisabled(options.hoursDisabled || this.element.data('date-hour-disabled')); 264 | this.fillDow(); 265 | this.fillMonths(); 266 | this.update(); 267 | this.showMode(); 268 | 269 | if (this.isInline) { 270 | this.show(); 271 | } 272 | }; 273 | 274 | Datetimepicker.prototype = { 275 | constructor: Datetimepicker, 276 | 277 | _events: [], 278 | _attachEvents: function () { 279 | this._detachEvents(); 280 | if (this.isInput) { // single input 281 | this._events = [ 282 | [this.element, { 283 | focus: $.proxy(this.show, this), 284 | keyup: $.proxy(this.update, this), 285 | keydown: $.proxy(this.keydown, this) 286 | }] 287 | ]; 288 | } 289 | else if (this.component && this.hasInput) { // component: input + button 290 | this._events = [ 291 | // For components that are not readonly, allow keyboard nav 292 | [this.element.find('input'), { 293 | focus: $.proxy(this.show, this), 294 | keyup: $.proxy(this.update, this), 295 | keydown: $.proxy(this.keydown, this) 296 | }], 297 | [this.component, { 298 | click: $.proxy(this.show, this) 299 | }] 300 | ]; 301 | if (this.componentReset) { 302 | this._events.push([ 303 | this.componentReset, 304 | {click: $.proxy(this.reset, this)} 305 | ]); 306 | } 307 | } 308 | else if (this.element.is('div')) { // inline datetimepicker 309 | this.isInline = true; 310 | } 311 | else { 312 | this._events = [ 313 | [this.element, { 314 | click: $.proxy(this.show, this) 315 | }] 316 | ]; 317 | } 318 | for (var i = 0, el, ev; i < this._events.length; i++) { 319 | el = this._events[i][0]; 320 | ev = this._events[i][1]; 321 | el.on(ev); 322 | } 323 | }, 324 | 325 | _detachEvents: function () { 326 | for (var i = 0, el, ev; i < this._events.length; i++) { 327 | el = this._events[i][0]; 328 | ev = this._events[i][1]; 329 | el.off(ev); 330 | } 331 | this._events = []; 332 | }, 333 | 334 | show: function (e) { 335 | this.picker.show(); 336 | this.height = this.component ? this.component.outerHeight() : this.element.outerHeight(); 337 | if (this.forceParse) { 338 | this.update(); 339 | } 340 | this.place(); 341 | $(window).on('resize', $.proxy(this.place, this)); 342 | if (e) { 343 | e.stopPropagation(); 344 | e.preventDefault(); 345 | } 346 | this.isVisible = true; 347 | this.element.trigger({ 348 | type: 'show', 349 | date: this.date 350 | }); 351 | }, 352 | 353 | hide: function (e) { 354 | if (!this.isVisible) return; 355 | if (this.isInline) return; 356 | this.picker.hide(); 357 | $(window).off('resize', this.place); 358 | this.viewMode = this.startViewMode; 359 | this.showMode(); 360 | if (!this.isInput) { 361 | $(document).off('mousedown', this.hide); 362 | } 363 | 364 | if ( 365 | this.forceParse && 366 | ( 367 | this.isInput && this.element.val() || 368 | this.hasInput && this.element.find('input').val() 369 | ) 370 | ) 371 | this.setValue(); 372 | this.isVisible = false; 373 | this.element.trigger({ 374 | type: 'hide', 375 | date: this.date 376 | }); 377 | }, 378 | 379 | remove: function () { 380 | this._detachEvents(); 381 | $(document).off('mousedown', this.clickedOutside); 382 | this.picker.remove(); 383 | delete this.picker; 384 | delete this.element.data().datetimepicker; 385 | }, 386 | 387 | getDate: function () { 388 | var d = this.getUTCDate(); 389 | return new Date(d.getTime() + (d.getTimezoneOffset() * 60000)); 390 | }, 391 | 392 | getUTCDate: function () { 393 | return this.date; 394 | }, 395 | 396 | getInitialDate: function () { 397 | return this.initialDate 398 | }, 399 | 400 | setInitialDate: function (initialDate) { 401 | this.initialDate = initialDate; 402 | }, 403 | 404 | setDate: function (d) { 405 | this.setUTCDate(new Date(d.getTime() - (d.getTimezoneOffset() * 60000))); 406 | }, 407 | 408 | setUTCDate: function (d) { 409 | if (d >= this.startDate && d <= this.endDate) { 410 | this.date = d; 411 | this.setValue(); 412 | this.viewDate = this.date; 413 | this.fill(); 414 | } else { 415 | this.element.trigger({ 416 | type: 'outOfRange', 417 | date: d, 418 | startDate: this.startDate, 419 | endDate: this.endDate 420 | }); 421 | } 422 | }, 423 | 424 | setFormat: function (format) { 425 | this.format = DPGlobal.parseFormat(format, this.formatType); 426 | var element; 427 | if (this.isInput) { 428 | element = this.element; 429 | } else if (this.component) { 430 | element = this.element.find('input'); 431 | } 432 | if (element && element.val()) { 433 | this.setValue(); 434 | } 435 | }, 436 | 437 | setValue: function () { 438 | var formatted = this.getFormattedDate(); 439 | if (!this.isInput) { 440 | if (this.component) { 441 | this.element.find('input').val(formatted); 442 | } 443 | this.element.data('date', formatted); 444 | } else { 445 | this.element.val(formatted); 446 | } 447 | if (this.linkField) { 448 | $('#' + this.linkField).val(this.getFormattedDate(this.linkFormat)); 449 | } 450 | }, 451 | 452 | getFormattedDate: function (format) { 453 | if (format == undefined) format = this.format; 454 | return DPGlobal.formatDate(this.date, format, this.language, this.formatType); 455 | }, 456 | 457 | setStartDate: function (startDate) { 458 | this.startDate = startDate || -Infinity; 459 | if (this.startDate !== -Infinity) { 460 | this.startDate = DPGlobal.parseDate(this.startDate, this.format, this.language, this.formatType); 461 | } 462 | this.update(); 463 | this.updateNavArrows(); 464 | }, 465 | 466 | setEndDate: function (endDate) { 467 | this.endDate = endDate || Infinity; 468 | if (this.endDate !== Infinity) { 469 | this.endDate = DPGlobal.parseDate(this.endDate, this.format, this.language, this.formatType); 470 | } 471 | this.update(); 472 | this.updateNavArrows(); 473 | }, 474 | 475 | setDatesDisabled: function (datesDisabled) { 476 | this.datesDisabled = datesDisabled || []; 477 | if (!$.isArray(this.datesDisabled)) { 478 | this.datesDisabled = this.datesDisabled.split(/,\s*/); 479 | } 480 | this.datesDisabled = $.map(this.datesDisabled, function (d) { 481 | return DPGlobal.parseDate(d, this.format, this.language, this.formatType).toDateString(); 482 | }); 483 | this.update(); 484 | this.updateNavArrows(); 485 | }, 486 | 487 | setTitle: function (selector, value) { 488 | return this.picker.find(selector) 489 | .find('th:eq(1)') 490 | .text(this.title === false ? value : this.title); 491 | }, 492 | 493 | setDaysOfWeekDisabled: function (daysOfWeekDisabled) { 494 | this.daysOfWeekDisabled = daysOfWeekDisabled || []; 495 | if (!$.isArray(this.daysOfWeekDisabled)) { 496 | this.daysOfWeekDisabled = this.daysOfWeekDisabled.split(/,\s*/); 497 | } 498 | this.daysOfWeekDisabled = $.map(this.daysOfWeekDisabled, function (d) { 499 | return parseInt(d, 10); 500 | }); 501 | this.update(); 502 | this.updateNavArrows(); 503 | }, 504 | 505 | setMinutesDisabled: function (minutesDisabled) { 506 | this.minutesDisabled = minutesDisabled || []; 507 | if (!$.isArray(this.minutesDisabled)) { 508 | this.minutesDisabled = this.minutesDisabled.split(/,\s*/); 509 | } 510 | this.minutesDisabled = $.map(this.minutesDisabled, function (d) { 511 | return parseInt(d, 10); 512 | }); 513 | this.update(); 514 | this.updateNavArrows(); 515 | }, 516 | 517 | setHoursDisabled: function (hoursDisabled) { 518 | this.hoursDisabled = hoursDisabled || []; 519 | if (!$.isArray(this.hoursDisabled)) { 520 | this.hoursDisabled = this.hoursDisabled.split(/,\s*/); 521 | } 522 | this.hoursDisabled = $.map(this.hoursDisabled, function (d) { 523 | return parseInt(d, 10); 524 | }); 525 | this.update(); 526 | this.updateNavArrows(); 527 | }, 528 | 529 | place: function () { 530 | if (this.isInline) return; 531 | 532 | if (!this.zIndex) { 533 | var index_highest = 0; 534 | $('div').each(function () { 535 | var index_current = parseInt($(this).css('zIndex'), 10); 536 | if (index_current > index_highest) { 537 | index_highest = index_current; 538 | } 539 | }); 540 | this.zIndex = index_highest + 10; 541 | } 542 | 543 | var offset, top, left, containerOffset; 544 | if (this.container instanceof $) { 545 | containerOffset = this.container.offset(); 546 | } else { 547 | containerOffset = $(this.container).offset(); 548 | } 549 | 550 | if (this.component) { 551 | offset = this.component.offset(); 552 | left = offset.left; 553 | if (this.pickerPosition == 'bottom-left' || this.pickerPosition == 'top-left') { 554 | left += this.component.outerWidth() - this.picker.outerWidth(); 555 | } 556 | } else { 557 | offset = this.element.offset(); 558 | left = offset.left; 559 | if (this.pickerPosition == 'bottom-left' || this.pickerPosition == 'top-left') { 560 | left += this.element.outerWidth() - this.picker.outerWidth(); 561 | } 562 | } 563 | 564 | var bodyWidth = document.body.clientWidth || window.innerWidth; 565 | if (left + 220 > bodyWidth) { 566 | left = bodyWidth - 220; 567 | } 568 | 569 | if (this.component) { 570 | top = top - containerOffset.top + 169; 571 | left = left - containerOffset.left + 210; 572 | } else { 573 | if (this.pickerPosition == 'top-left' || this.pickerPosition == 'top-right') { 574 | top = offset.top - this.picker.outerHeight(); 575 | } else { 576 | top = offset.top + this.height; 577 | } 578 | } 579 | 580 | this.picker.css({ 581 | top: top, 582 | left: left, 583 | zIndex: this.zIndex 584 | }); 585 | }, 586 | 587 | update: function () { 588 | var date, fromArgs = false; 589 | if (arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) { 590 | date = arguments[0]; 591 | fromArgs = true; 592 | } else { 593 | date = (this.isInput ? this.element.val() : this.element.find('input').val()) || this.element.data('date') || this.initialDate; 594 | if (typeof date == 'string' || date instanceof String) { 595 | date = date.replace(/^\s+|\s+$/g,''); 596 | } 597 | } 598 | 599 | if (!date) { 600 | date = new Date(); 601 | fromArgs = false; 602 | } 603 | 604 | this.date = DPGlobal.parseDate(date, this.format, this.language, this.formatType); 605 | 606 | if (fromArgs) this.setValue(); 607 | 608 | if (this.date < this.startDate) { 609 | this.viewDate = new Date(this.startDate); 610 | } else if (this.date > this.endDate) { 611 | this.viewDate = new Date(this.endDate); 612 | } else { 613 | this.viewDate = new Date(this.date); 614 | } 615 | this.fill(); 616 | }, 617 | 618 | fillDow: function () { 619 | var dowCnt = this.weekStart, 620 | html = ''; 621 | while (dowCnt < this.weekStart + 7) { 622 | html += '' + dates[this.language].daysMin[(dowCnt++) % 7] + ''; 623 | } 624 | html += ''; 625 | this.picker.find('.datetimepicker-days thead').append(html); 626 | }, 627 | 628 | fillMonths: function () { 629 | var html = '', 630 | i = 0; 631 | while (i < 12) { 632 | html += '' + dates[this.language].monthsShort[i++] + ''; 633 | } 634 | this.picker.find('.datetimepicker-months td').html(html); 635 | }, 636 | 637 | fill: function () { 638 | if (this.date == null || this.viewDate == null) { 639 | return; 640 | } 641 | var d = new Date(this.viewDate), 642 | year = d.getUTCFullYear(), 643 | month = d.getUTCMonth(), 644 | dayMonth = d.getUTCDate(), 645 | hours = d.getUTCHours(), 646 | minutes = d.getUTCMinutes(), 647 | startYear = this.startDate !== -Infinity ? this.startDate.getUTCFullYear() : -Infinity, 648 | startMonth = this.startDate !== -Infinity ? this.startDate.getUTCMonth() + 1 : -Infinity, 649 | endYear = this.endDate !== Infinity ? this.endDate.getUTCFullYear() : Infinity, 650 | endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() + 1 : Infinity, 651 | currentDate = (new UTCDate(this.date.getUTCFullYear(), this.date.getUTCMonth(), this.date.getUTCDate())).valueOf(), 652 | today = new Date(); 653 | this.setTitle('.datetimepicker-days', dates[this.language].months[month] + ' ' + year) 654 | if (this.formatViewType == 'time') { 655 | var formatted = this.getFormattedDate(); 656 | this.setTitle('.datetimepicker-hours', formatted); 657 | this.setTitle('.datetimepicker-minutes', formatted); 658 | } else { 659 | this.setTitle('.datetimepicker-hours', dayMonth + ' ' + dates[this.language].months[month] + ' ' + year); 660 | this.setTitle('.datetimepicker-minutes', dayMonth + ' ' + dates[this.language].months[month] + ' ' + year); 661 | } 662 | this.picker.find('tfoot th.today') 663 | .text(dates[this.language].today || dates['en'].today) 664 | .toggle(this.todayBtn !== false); 665 | this.picker.find('tfoot th.clear') 666 | .text(dates[this.language].clear || dates['en'].clear) 667 | .toggle(this.clearBtn !== false); 668 | this.updateNavArrows(); 669 | this.fillMonths(); 670 | /*var prevMonth = UTCDate(year, month, 0,0,0,0,0); 671 | prevMonth.setUTCDate(prevMonth.getDate() - (prevMonth.getUTCDay() - this.weekStart + 7)%7);*/ 672 | var prevMonth = UTCDate(year, month - 1, 28, 0, 0, 0, 0), 673 | day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth()); 674 | prevMonth.setUTCDate(day); 675 | prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.weekStart + 7) % 7); 676 | var nextMonth = new Date(prevMonth); 677 | nextMonth.setUTCDate(nextMonth.getUTCDate() + 42); 678 | nextMonth = nextMonth.valueOf(); 679 | var html = []; 680 | var clsName; 681 | while (prevMonth.valueOf() < nextMonth) { 682 | if (prevMonth.getUTCDay() == this.weekStart) { 683 | html.push(''); 684 | } 685 | clsName = ''; 686 | if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() < month)) { 687 | clsName += ' old'; 688 | } else if (prevMonth.getUTCFullYear() > year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() > month)) { 689 | clsName += ' new'; 690 | } 691 | // Compare internal UTC date with local today, not UTC today 692 | if (this.todayHighlight && 693 | prevMonth.getUTCFullYear() == today.getFullYear() && 694 | prevMonth.getUTCMonth() == today.getMonth() && 695 | prevMonth.getUTCDate() == today.getDate()) { 696 | clsName += ' today'; 697 | } 698 | if (prevMonth.valueOf() == currentDate) { 699 | clsName += ' active'; 700 | } 701 | if ((prevMonth.valueOf() + 86400000) <= this.startDate || prevMonth.valueOf() > this.endDate || 702 | $.inArray(prevMonth.getUTCDay(), this.daysOfWeekDisabled) !== -1 || 703 | $.inArray(prevMonth.toDateString(), this.datesDisabled) !== -1) { 704 | clsName += ' disabled'; 705 | } 706 | html.push('' + prevMonth.getUTCDate() + ''); 707 | if (prevMonth.getUTCDay() == this.weekEnd) { 708 | html.push(''); 709 | } 710 | prevMonth.setUTCDate(prevMonth.getUTCDate() + 1); 711 | } 712 | this.picker.find('.datetimepicker-days tbody').empty().append(html.join('')); 713 | 714 | html = []; 715 | var txt = '', meridian = '', meridianOld = ''; 716 | var hoursDisabled = this.hoursDisabled || []; 717 | for (var i = 0; i < 24; i++) { 718 | if (hoursDisabled.indexOf(i) !== -1) continue; 719 | var actual = UTCDate(year, month, dayMonth, i); 720 | clsName = ''; 721 | // We want the previous hour for the startDate 722 | if ((actual.valueOf() + 3600000) <= this.startDate || actual.valueOf() > this.endDate) { 723 | clsName += ' disabled'; 724 | } else if (hours == i) { 725 | clsName += ' active'; 726 | } 727 | if (this.showMeridian && dates[this.language].meridiem.length == 2) { 728 | meridian = (i < 12 ? dates[this.language].meridiem[0] : dates[this.language].meridiem[1]); 729 | if (meridian != meridianOld) { 730 | if (meridianOld != '') { 731 | html.push(''); 732 | } 733 | html.push('
' + meridian.toUpperCase() + ''); 734 | } 735 | meridianOld = meridian; 736 | txt = (i % 12 ? i % 12 : 12); 737 | html.push('' + txt + ''); 738 | if (i == 23) { 739 | html.push('
'); 740 | } 741 | } else { 742 | txt = i + ':00'; 743 | html.push('' + txt + ''); 744 | } 745 | } 746 | this.picker.find('.datetimepicker-hours td').html(html.join('')); 747 | 748 | html = []; 749 | txt = '', meridian = '', meridianOld = ''; 750 | var minutesDisabled = this.minutesDisabled || []; 751 | for (var i = 0; i < 60; i += this.minuteStep) { 752 | if (minutesDisabled.indexOf(i) !== -1) continue; 753 | var actual = UTCDate(year, month, dayMonth, hours, i, 0); 754 | clsName = ''; 755 | if (actual.valueOf() < this.startDate || actual.valueOf() > this.endDate) { 756 | clsName += ' disabled'; 757 | } else if (Math.floor(minutes / this.minuteStep) == Math.floor(i / this.minuteStep)) { 758 | clsName += ' active'; 759 | } 760 | if (this.showMeridian && dates[this.language].meridiem.length == 2) { 761 | meridian = (hours < 12 ? dates[this.language].meridiem[0] : dates[this.language].meridiem[1]); 762 | if (meridian != meridianOld) { 763 | if (meridianOld != '') { 764 | html.push(''); 765 | } 766 | html.push('
' + meridian.toUpperCase() + ''); 767 | } 768 | meridianOld = meridian; 769 | txt = (hours % 12 ? hours % 12 : 12); 770 | //html.push(''+txt+''); 771 | html.push('' + txt + ':' + (i < 10 ? '0' + i : i) + ''); 772 | if (i == 59) { 773 | html.push('
'); 774 | } 775 | } else { 776 | txt = i + ':00'; 777 | //html.push(''+txt+''); 778 | html.push('' + hours + ':' + (i < 10 ? '0' + i : i) + ''); 779 | } 780 | } 781 | this.picker.find('.datetimepicker-minutes td').html(html.join('')); 782 | 783 | var currentYear = this.date.getUTCFullYear(); 784 | var months = this.setTitle('.datetimepicker-months', year) 785 | .end() 786 | .find('span').removeClass('active'); 787 | if (currentYear == year) { 788 | // getUTCMonths() returns 0 based, and we need to select the next one 789 | // To cater bootstrap 2 we don't need to select the next one 790 | var offset = months.length - 12; 791 | months.eq(this.date.getUTCMonth() + offset).addClass('active'); 792 | } 793 | if (year < startYear || year > endYear) { 794 | months.addClass('disabled'); 795 | } 796 | if (year == startYear) { 797 | months.slice(0, startMonth + 1).addClass('disabled'); 798 | } 799 | if (year == endYear) { 800 | months.slice(endMonth).addClass('disabled'); 801 | } 802 | 803 | html = ''; 804 | year = parseInt(year / 10, 10) * 10; 805 | var yearCont = this.setTitle('.datetimepicker-years', year + '-' + (year + 9)) 806 | .end() 807 | .find('td'); 808 | year -= 1; 809 | for (var i = -1; i < 11; i++) { 810 | html += '' + year + ''; 811 | year += 1; 812 | } 813 | yearCont.html(html); 814 | this.place(); 815 | }, 816 | 817 | updateNavArrows: function () { 818 | var d = new Date(this.viewDate), 819 | year = d.getUTCFullYear(), 820 | month = d.getUTCMonth(), 821 | day = d.getUTCDate(), 822 | hour = d.getUTCHours(); 823 | switch (this.viewMode) { 824 | case 0: 825 | if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear() 826 | && month <= this.startDate.getUTCMonth() 827 | && day <= this.startDate.getUTCDate() 828 | && hour <= this.startDate.getUTCHours()) { 829 | this.picker.find('.prev').css({visibility: 'hidden'}); 830 | } else { 831 | this.picker.find('.prev').css({visibility: 'visible'}); 832 | } 833 | if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear() 834 | && month >= this.endDate.getUTCMonth() 835 | && day >= this.endDate.getUTCDate() 836 | && hour >= this.endDate.getUTCHours()) { 837 | this.picker.find('.next').css({visibility: 'hidden'}); 838 | } else { 839 | this.picker.find('.next').css({visibility: 'visible'}); 840 | } 841 | break; 842 | case 1: 843 | if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear() 844 | && month <= this.startDate.getUTCMonth() 845 | && day <= this.startDate.getUTCDate()) { 846 | this.picker.find('.prev').css({visibility: 'hidden'}); 847 | } else { 848 | this.picker.find('.prev').css({visibility: 'visible'}); 849 | } 850 | if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear() 851 | && month >= this.endDate.getUTCMonth() 852 | && day >= this.endDate.getUTCDate()) { 853 | this.picker.find('.next').css({visibility: 'hidden'}); 854 | } else { 855 | this.picker.find('.next').css({visibility: 'visible'}); 856 | } 857 | break; 858 | case 2: 859 | if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear() 860 | && month <= this.startDate.getUTCMonth()) { 861 | this.picker.find('.prev').css({visibility: 'hidden'}); 862 | } else { 863 | this.picker.find('.prev').css({visibility: 'visible'}); 864 | } 865 | if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear() 866 | && month >= this.endDate.getUTCMonth()) { 867 | this.picker.find('.next').css({visibility: 'hidden'}); 868 | } else { 869 | this.picker.find('.next').css({visibility: 'visible'}); 870 | } 871 | break; 872 | case 3: 873 | case 4: 874 | if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()) { 875 | this.picker.find('.prev').css({visibility: 'hidden'}); 876 | } else { 877 | this.picker.find('.prev').css({visibility: 'visible'}); 878 | } 879 | if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()) { 880 | this.picker.find('.next').css({visibility: 'hidden'}); 881 | } else { 882 | this.picker.find('.next').css({visibility: 'visible'}); 883 | } 884 | break; 885 | } 886 | }, 887 | 888 | mousewheel: function (e) { 889 | 890 | e.preventDefault(); 891 | e.stopPropagation(); 892 | 893 | if (this.wheelPause) { 894 | return; 895 | } 896 | 897 | this.wheelPause = true; 898 | 899 | var originalEvent = e.originalEvent; 900 | 901 | var delta = originalEvent.wheelDelta; 902 | 903 | var mode = delta > 0 ? 1 : (delta === 0) ? 0 : -1; 904 | 905 | if (this.wheelViewModeNavigationInverseDirection) { 906 | mode = -mode; 907 | } 908 | 909 | this.showMode(mode); 910 | 911 | setTimeout($.proxy(function () { 912 | 913 | this.wheelPause = false 914 | 915 | }, this), this.wheelViewModeNavigationDelay); 916 | 917 | }, 918 | 919 | click: function (e) { 920 | e.stopPropagation(); 921 | e.preventDefault(); 922 | var target = $(e.target).closest('span, td, th, legend'); 923 | if (target.is('.' + this.icontype)) { 924 | target = $(target).parent().closest('span, td, th, legend'); 925 | } 926 | if (target.length == 1) { 927 | if (target.is('.disabled')) { 928 | this.element.trigger({ 929 | type: 'outOfRange', 930 | date: this.viewDate, 931 | startDate: this.startDate, 932 | endDate: this.endDate 933 | }); 934 | return; 935 | } 936 | switch (target[0].nodeName.toLowerCase()) { 937 | case 'th': 938 | switch (target[0].className) { 939 | case 'switch': 940 | this.showMode(1); 941 | break; 942 | case 'prev': 943 | case 'next': 944 | var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1); 945 | switch (this.viewMode) { 946 | case 0: 947 | this.viewDate = this.moveHour(this.viewDate, dir); 948 | break; 949 | case 1: 950 | this.viewDate = this.moveDate(this.viewDate, dir); 951 | break; 952 | case 2: 953 | this.viewDate = this.moveMonth(this.viewDate, dir); 954 | break; 955 | case 3: 956 | case 4: 957 | this.viewDate = this.moveYear(this.viewDate, dir); 958 | break; 959 | } 960 | this.fill(); 961 | this.element.trigger({ 962 | type: target[0].className + ':' + this.convertViewModeText(this.viewMode), 963 | date: this.viewDate, 964 | startDate: this.startDate, 965 | endDate: this.endDate 966 | }); 967 | break; 968 | case 'clear': 969 | this.reset(); 970 | if (this.autoclose) { 971 | this.hide(); 972 | } 973 | break; 974 | case 'today': 975 | var date = new Date(); 976 | date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), 0); 977 | 978 | // Respect startDate and endDate. 979 | if (date < this.startDate) date = this.startDate; 980 | else if (date > this.endDate) date = this.endDate; 981 | 982 | this.viewMode = this.startViewMode; 983 | this.showMode(0); 984 | this._setDate(date); 985 | this.fill(); 986 | if (this.autoclose) { 987 | this.hide(); 988 | } 989 | break; 990 | } 991 | break; 992 | case 'span': 993 | if (!target.is('.disabled')) { 994 | var year = this.viewDate.getUTCFullYear(), 995 | month = this.viewDate.getUTCMonth(), 996 | day = this.viewDate.getUTCDate(), 997 | hours = this.viewDate.getUTCHours(), 998 | minutes = this.viewDate.getUTCMinutes(), 999 | seconds = this.viewDate.getUTCSeconds(); 1000 | 1001 | if (target.is('.month')) { 1002 | this.viewDate.setUTCDate(1); 1003 | month = target.parent().find('span').index(target); 1004 | day = this.viewDate.getUTCDate(); 1005 | this.viewDate.setUTCMonth(month); 1006 | this.element.trigger({ 1007 | type: 'changeMonth', 1008 | date: this.viewDate 1009 | }); 1010 | if (this.viewSelect >= 3) { 1011 | this._setDate(UTCDate(year, month, day, hours, minutes, seconds, 0)); 1012 | } 1013 | } else if (target.is('.year')) { 1014 | this.viewDate.setUTCDate(1); 1015 | year = parseInt(target.text(), 10) || 0; 1016 | this.viewDate.setUTCFullYear(year); 1017 | this.element.trigger({ 1018 | type: 'changeYear', 1019 | date: this.viewDate 1020 | }); 1021 | if (this.viewSelect >= 4) { 1022 | this._setDate(UTCDate(year, month, day, hours, minutes, seconds, 0)); 1023 | } 1024 | } else if (target.is('.hour')) { 1025 | hours = parseInt(target.text(), 10) || 0; 1026 | if (target.hasClass('hour_am') || target.hasClass('hour_pm')) { 1027 | if (hours == 12 && target.hasClass('hour_am')) { 1028 | hours = 0; 1029 | } else if (hours != 12 && target.hasClass('hour_pm')) { 1030 | hours += 12; 1031 | } 1032 | } 1033 | this.viewDate.setUTCHours(hours); 1034 | this.element.trigger({ 1035 | type: 'changeHour', 1036 | date: this.viewDate 1037 | }); 1038 | if (this.viewSelect >= 1) { 1039 | this._setDate(UTCDate(year, month, day, hours, minutes, seconds, 0)); 1040 | } 1041 | } else if (target.is('.minute')) { 1042 | minutes = parseInt(target.text().substr(target.text().indexOf(':') + 1), 10) || 0; 1043 | this.viewDate.setUTCMinutes(minutes); 1044 | this.element.trigger({ 1045 | type: 'changeMinute', 1046 | date: this.viewDate 1047 | }); 1048 | if (this.viewSelect >= 0) { 1049 | this._setDate(UTCDate(year, month, day, hours, minutes, seconds, 0)); 1050 | } 1051 | } 1052 | if (this.viewMode != 0) { 1053 | var oldViewMode = this.viewMode; 1054 | this.showMode(-1); 1055 | this.fill(); 1056 | if (oldViewMode == this.viewMode && this.autoclose) { 1057 | this.hide(); 1058 | } 1059 | } else { 1060 | this.fill(); 1061 | if (this.autoclose) { 1062 | this.hide(); 1063 | } 1064 | } 1065 | } 1066 | break; 1067 | case 'td': 1068 | if (target.is('.day') && !target.is('.disabled')) { 1069 | var day = parseInt(target.text(), 10) || 1; 1070 | var year = this.viewDate.getUTCFullYear(), 1071 | month = this.viewDate.getUTCMonth(), 1072 | hours = this.viewDate.getUTCHours(), 1073 | minutes = this.viewDate.getUTCMinutes(), 1074 | seconds = this.viewDate.getUTCSeconds(); 1075 | if (target.is('.old')) { 1076 | if (month === 0) { 1077 | month = 11; 1078 | year -= 1; 1079 | } else { 1080 | month -= 1; 1081 | } 1082 | } else if (target.is('.new')) { 1083 | if (month == 11) { 1084 | month = 0; 1085 | year += 1; 1086 | } else { 1087 | month += 1; 1088 | } 1089 | } 1090 | this.viewDate.setUTCFullYear(year); 1091 | this.viewDate.setUTCMonth(month, day); 1092 | this.element.trigger({ 1093 | type: 'changeDay', 1094 | date: this.viewDate 1095 | }); 1096 | if (this.viewSelect >= 2) { 1097 | this._setDate(UTCDate(year, month, day, hours, minutes, seconds, 0)); 1098 | } 1099 | } 1100 | var oldViewMode = this.viewMode; 1101 | this.showMode(-1); 1102 | this.fill(); 1103 | if (oldViewMode == this.viewMode && this.autoclose) { 1104 | this.hide(); 1105 | } 1106 | break; 1107 | } 1108 | } 1109 | }, 1110 | 1111 | _setDate: function (date, which) { 1112 | if (!which || which == 'date') 1113 | this.date = date; 1114 | if (!which || which == 'view') 1115 | this.viewDate = date; 1116 | this.fill(); 1117 | this.setValue(); 1118 | var element; 1119 | if (this.isInput) { 1120 | element = this.element; 1121 | } else if (this.component) { 1122 | element = this.element.find('input'); 1123 | } 1124 | if (element) { 1125 | element.change(); 1126 | if (this.autoclose && (!which || which == 'date')) { 1127 | //this.hide(); 1128 | } 1129 | } 1130 | this.element.trigger({ 1131 | type: 'changeDate', 1132 | date: this.getDate() 1133 | }); 1134 | if(date == null) 1135 | this.date = this.viewDate; 1136 | }, 1137 | 1138 | moveMinute: function (date, dir) { 1139 | if (!dir) return date; 1140 | var new_date = new Date(date.valueOf()); 1141 | //dir = dir > 0 ? 1 : -1; 1142 | new_date.setUTCMinutes(new_date.getUTCMinutes() + (dir * this.minuteStep)); 1143 | return new_date; 1144 | }, 1145 | 1146 | moveHour: function (date, dir) { 1147 | if (!dir) return date; 1148 | var new_date = new Date(date.valueOf()); 1149 | //dir = dir > 0 ? 1 : -1; 1150 | new_date.setUTCHours(new_date.getUTCHours() + dir); 1151 | return new_date; 1152 | }, 1153 | 1154 | moveDate: function (date, dir) { 1155 | if (!dir) return date; 1156 | var new_date = new Date(date.valueOf()); 1157 | //dir = dir > 0 ? 1 : -1; 1158 | new_date.setUTCDate(new_date.getUTCDate() + dir); 1159 | return new_date; 1160 | }, 1161 | 1162 | moveMonth: function (date, dir) { 1163 | if (!dir) return date; 1164 | var new_date = new Date(date.valueOf()), 1165 | day = new_date.getUTCDate(), 1166 | month = new_date.getUTCMonth(), 1167 | mag = Math.abs(dir), 1168 | new_month, test; 1169 | dir = dir > 0 ? 1 : -1; 1170 | if (mag == 1) { 1171 | test = dir == -1 1172 | // If going back one month, make sure month is not current month 1173 | // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02) 1174 | ? function () { 1175 | return new_date.getUTCMonth() == month; 1176 | } 1177 | // If going forward one month, make sure month is as expected 1178 | // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02) 1179 | : function () { 1180 | return new_date.getUTCMonth() != new_month; 1181 | }; 1182 | new_month = month + dir; 1183 | new_date.setUTCMonth(new_month); 1184 | // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11 1185 | if (new_month < 0 || new_month > 11) 1186 | new_month = (new_month + 12) % 12; 1187 | } else { 1188 | // For magnitudes >1, move one month at a time... 1189 | for (var i = 0; i < mag; i++) 1190 | // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)... 1191 | new_date = this.moveMonth(new_date, dir); 1192 | // ...then reset the day, keeping it in the new month 1193 | new_month = new_date.getUTCMonth(); 1194 | new_date.setUTCDate(day); 1195 | test = function () { 1196 | return new_month != new_date.getUTCMonth(); 1197 | }; 1198 | } 1199 | // Common date-resetting loop -- if date is beyond end of month, make it 1200 | // end of month 1201 | while (test()) { 1202 | new_date.setUTCDate(--day); 1203 | new_date.setUTCMonth(new_month); 1204 | } 1205 | return new_date; 1206 | }, 1207 | 1208 | moveYear: function (date, dir) { 1209 | return this.moveMonth(date, dir * 12); 1210 | }, 1211 | 1212 | dateWithinRange: function (date) { 1213 | return date >= this.startDate && date <= this.endDate; 1214 | }, 1215 | 1216 | keydown: function (e) { 1217 | if (this.picker.is(':not(:visible)')) { 1218 | if (e.keyCode == 27) // allow escape to hide and re-show picker 1219 | this.show(); 1220 | return; 1221 | } 1222 | var dateChanged = false, 1223 | dir, day, month, 1224 | newDate, newViewDate; 1225 | switch (e.keyCode) { 1226 | case 27: // escape 1227 | this.hide(); 1228 | e.preventDefault(); 1229 | break; 1230 | case 37: // left 1231 | case 39: // right 1232 | if (!this.keyboardNavigation) break; 1233 | dir = e.keyCode == 37 ? -1 : 1; 1234 | viewMode = this.viewMode; 1235 | if (e.ctrlKey) { 1236 | viewMode += 2; 1237 | } else if (e.shiftKey) { 1238 | viewMode += 1; 1239 | } 1240 | if (viewMode == 4) { 1241 | newDate = this.moveYear(this.date, dir); 1242 | newViewDate = this.moveYear(this.viewDate, dir); 1243 | } else if (viewMode == 3) { 1244 | newDate = this.moveMonth(this.date, dir); 1245 | newViewDate = this.moveMonth(this.viewDate, dir); 1246 | } else if (viewMode == 2) { 1247 | newDate = this.moveDate(this.date, dir); 1248 | newViewDate = this.moveDate(this.viewDate, dir); 1249 | } else if (viewMode == 1) { 1250 | newDate = this.moveHour(this.date, dir); 1251 | newViewDate = this.moveHour(this.viewDate, dir); 1252 | } else if (viewMode == 0) { 1253 | newDate = this.moveMinute(this.date, dir); 1254 | newViewDate = this.moveMinute(this.viewDate, dir); 1255 | } 1256 | if (this.dateWithinRange(newDate)) { 1257 | this.date = newDate; 1258 | this.viewDate = newViewDate; 1259 | this.setValue(); 1260 | this.update(); 1261 | e.preventDefault(); 1262 | dateChanged = true; 1263 | } 1264 | break; 1265 | case 38: // up 1266 | case 40: // down 1267 | if (!this.keyboardNavigation) break; 1268 | dir = e.keyCode == 38 ? -1 : 1; 1269 | viewMode = this.viewMode; 1270 | if (e.ctrlKey) { 1271 | viewMode += 2; 1272 | } else if (e.shiftKey) { 1273 | viewMode += 1; 1274 | } 1275 | if (viewMode == 4) { 1276 | newDate = this.moveYear(this.date, dir); 1277 | newViewDate = this.moveYear(this.viewDate, dir); 1278 | } else if (viewMode == 3) { 1279 | newDate = this.moveMonth(this.date, dir); 1280 | newViewDate = this.moveMonth(this.viewDate, dir); 1281 | } else if (viewMode == 2) { 1282 | newDate = this.moveDate(this.date, dir * 7); 1283 | newViewDate = this.moveDate(this.viewDate, dir * 7); 1284 | } else if (viewMode == 1) { 1285 | if (this.showMeridian) { 1286 | newDate = this.moveHour(this.date, dir * 6); 1287 | newViewDate = this.moveHour(this.viewDate, dir * 6); 1288 | } else { 1289 | newDate = this.moveHour(this.date, dir * 4); 1290 | newViewDate = this.moveHour(this.viewDate, dir * 4); 1291 | } 1292 | } else if (viewMode == 0) { 1293 | newDate = this.moveMinute(this.date, dir * 4); 1294 | newViewDate = this.moveMinute(this.viewDate, dir * 4); 1295 | } 1296 | if (this.dateWithinRange(newDate)) { 1297 | this.date = newDate; 1298 | this.viewDate = newViewDate; 1299 | this.setValue(); 1300 | this.update(); 1301 | e.preventDefault(); 1302 | dateChanged = true; 1303 | } 1304 | break; 1305 | case 13: // enter 1306 | if (this.viewMode != 0) { 1307 | var oldViewMode = this.viewMode; 1308 | this.showMode(-1); 1309 | this.fill(); 1310 | if (oldViewMode == this.viewMode && this.autoclose) { 1311 | this.hide(); 1312 | } 1313 | } else { 1314 | this.fill(); 1315 | if (this.autoclose) { 1316 | this.hide(); 1317 | } 1318 | } 1319 | e.preventDefault(); 1320 | break; 1321 | case 9: // tab 1322 | this.hide(); 1323 | break; 1324 | } 1325 | if (dateChanged) { 1326 | var element; 1327 | if (this.isInput) { 1328 | element = this.element; 1329 | } else if (this.component) { 1330 | element = this.element.find('input'); 1331 | } 1332 | if (element) { 1333 | element.change(); 1334 | } 1335 | this.element.trigger({ 1336 | type: 'changeDate', 1337 | date: this.getDate() 1338 | }); 1339 | } 1340 | }, 1341 | 1342 | showMode: function (dir) { 1343 | if (dir) { 1344 | var newViewMode = Math.max(0, Math.min(DPGlobal.modes.length - 1, this.viewMode + dir)); 1345 | if (newViewMode >= this.minView && newViewMode <= this.maxView) { 1346 | this.element.trigger({ 1347 | type: 'changeMode', 1348 | date: this.viewDate, 1349 | oldViewMode: this.viewMode, 1350 | newViewMode: newViewMode 1351 | }); 1352 | 1353 | this.viewMode = newViewMode; 1354 | } 1355 | } 1356 | /* 1357 | vitalets: fixing bug of very special conditions: 1358 | jquery 1.7.1 + webkit + show inline datetimepicker in bootstrap popover. 1359 | Method show() does not set display css correctly and datetimepicker is not shown. 1360 | Changed to .css('display', 'block') solve the problem. 1361 | See https://github.com/vitalets/x-editable/issues/37 1362 | 1363 | In jquery 1.7.2+ everything works fine. 1364 | */ 1365 | //this.picker.find('>div').hide().filter('.datetimepicker-'+DPGlobal.modes[this.viewMode].clsName).show(); 1366 | this.picker.find('>div').hide().filter('.datetimepicker-' + DPGlobal.modes[this.viewMode].clsName).css('display', 'block'); 1367 | this.updateNavArrows(); 1368 | }, 1369 | 1370 | reset: function (e) { 1371 | this._setDate(null, 'date'); 1372 | }, 1373 | 1374 | convertViewModeText: function (viewMode) { 1375 | switch (viewMode) { 1376 | case 4: 1377 | return 'decade'; 1378 | case 3: 1379 | return 'year'; 1380 | case 2: 1381 | return 'month'; 1382 | case 1: 1383 | return 'day'; 1384 | case 0: 1385 | return 'hour'; 1386 | } 1387 | } 1388 | }; 1389 | 1390 | var old = $.fn.datetimepicker; 1391 | $.fn.datetimepicker = function (option) { 1392 | var args = Array.apply(null, arguments); 1393 | args.shift(); 1394 | var internal_return; 1395 | this.each(function () { 1396 | var $this = $(this), 1397 | data = $this.data('datetimepicker'), 1398 | options = typeof option == 'object' && option; 1399 | if (!data) { 1400 | $this.data('datetimepicker', (data = new Datetimepicker(this, $.extend({}, $.fn.datetimepicker.defaults, options)))); 1401 | } 1402 | if (typeof option == 'string' && typeof data[option] == 'function') { 1403 | internal_return = data[option].apply(data, args); 1404 | if (internal_return !== undefined) { 1405 | return false; 1406 | } 1407 | } 1408 | }); 1409 | if (internal_return !== undefined) 1410 | return internal_return; 1411 | else 1412 | return this; 1413 | }; 1414 | 1415 | $.fn.datetimepicker.defaults = { 1416 | }; 1417 | $.fn.datetimepicker.Constructor = Datetimepicker; 1418 | var dates = $.fn.datetimepicker.dates = { 1419 | en: { 1420 | days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'], 1421 | daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], 1422 | daysMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'], 1423 | months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], 1424 | monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], 1425 | meridiem: ['am', 'pm'], 1426 | suffix: ['st', 'nd', 'rd', 'th'], 1427 | today: 'Today', 1428 | clear: 'Clear' 1429 | } 1430 | }; 1431 | 1432 | var DPGlobal = { 1433 | modes: [ 1434 | { 1435 | clsName: 'minutes', 1436 | navFnc: 'Hours', 1437 | navStep: 1 1438 | }, 1439 | { 1440 | clsName: 'hours', 1441 | navFnc: 'Date', 1442 | navStep: 1 1443 | }, 1444 | { 1445 | clsName: 'days', 1446 | navFnc: 'Month', 1447 | navStep: 1 1448 | }, 1449 | { 1450 | clsName: 'months', 1451 | navFnc: 'FullYear', 1452 | navStep: 1 1453 | }, 1454 | { 1455 | clsName: 'years', 1456 | navFnc: 'FullYear', 1457 | navStep: 10 1458 | } 1459 | ], 1460 | isLeapYear: function (year) { 1461 | return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)) 1462 | }, 1463 | getDaysInMonth: function (year, month) { 1464 | return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month] 1465 | }, 1466 | getDefaultFormat: function (type, field) { 1467 | if (type == 'standard') { 1468 | if (field == 'input') 1469 | return 'yyyy-mm-dd hh:ii'; 1470 | else 1471 | return 'yyyy-mm-dd hh:ii:ss'; 1472 | } else if (type == 'php') { 1473 | if (field == 'input') 1474 | return 'Y-m-d H:i'; 1475 | else 1476 | return 'Y-m-d H:i:s'; 1477 | } else { 1478 | throw new Error('Invalid format type.'); 1479 | } 1480 | }, 1481 | validParts: function (type) { 1482 | if (type == 'standard') { 1483 | return /t|hh?|HH?|p|P|ii?|ss?|dd?|DD?|mm?|MM?|yy(?:yy)?/g; 1484 | } else if (type == 'php') { 1485 | return /[dDjlNwzFmMnStyYaABgGhHis]/g; 1486 | } else { 1487 | throw new Error('Invalid format type.'); 1488 | } 1489 | }, 1490 | nonpunctuation: /[^ -\/:-@\[-`{-~\t\n\rTZ]+/g, 1491 | parseFormat: function (format, type) { 1492 | // IE treats \0 as a string end in inputs (truncating the value), 1493 | // so it's a bad format delimiter, anyway 1494 | var separators = format.replace(this.validParts(type), '\0').split('\0'), 1495 | parts = format.match(this.validParts(type)); 1496 | if (!separators || !separators.length || !parts || parts.length == 0) { 1497 | throw new Error('Invalid date format.'); 1498 | } 1499 | return {separators: separators, parts: parts}; 1500 | }, 1501 | parseDate: function (date, format, language, type) { 1502 | if (date instanceof Date) { 1503 | var dateUTC = new Date(date.valueOf() - date.getTimezoneOffset() * 60000); 1504 | dateUTC.setMilliseconds(0); 1505 | return dateUTC; 1506 | } 1507 | if (/^\d{4}\-\d{1,2}\-\d{1,2}$/.test(date)) { 1508 | format = this.parseFormat('yyyy-mm-dd', type); 1509 | } 1510 | if (/^\d{4}\-\d{1,2}\-\d{1,2}[T ]\d{1,2}\:\d{1,2}$/.test(date)) { 1511 | format = this.parseFormat('yyyy-mm-dd hh:ii', type); 1512 | } 1513 | if (/^\d{4}\-\d{1,2}\-\d{1,2}[T ]\d{1,2}\:\d{1,2}\:\d{1,2}[Z]{0,1}$/.test(date)) { 1514 | format = this.parseFormat('yyyy-mm-dd hh:ii:ss', type); 1515 | } 1516 | if (/^[-+]\d+[dmwy]([\s,]+[-+]\d+[dmwy])*$/.test(date)) { 1517 | var part_re = /([-+]\d+)([dmwy])/, 1518 | parts = date.match(/([-+]\d+)([dmwy])/g), 1519 | part, dir; 1520 | date = new Date(); 1521 | for (var i = 0; i < parts.length; i++) { 1522 | part = part_re.exec(parts[i]); 1523 | dir = parseInt(part[1]); 1524 | switch (part[2]) { 1525 | case 'd': 1526 | date.setUTCDate(date.getUTCDate() + dir); 1527 | break; 1528 | case 'm': 1529 | date = Datetimepicker.prototype.moveMonth.call(Datetimepicker.prototype, date, dir); 1530 | break; 1531 | case 'w': 1532 | date.setUTCDate(date.getUTCDate() + dir * 7); 1533 | break; 1534 | case 'y': 1535 | date = Datetimepicker.prototype.moveYear.call(Datetimepicker.prototype, date, dir); 1536 | break; 1537 | } 1538 | } 1539 | return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), 0); 1540 | } 1541 | var parts = date && date.toString().match(this.nonpunctuation) || [], 1542 | date = new Date(0, 0, 0, 0, 0, 0, 0), 1543 | parsed = {}, 1544 | setters_order = ['hh', 'h', 'ii', 'i', 'ss', 's', 'yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'D', 'DD', 'd', 'dd', 'H', 'HH', 'p', 'P'], 1545 | setters_map = { 1546 | hh: function (d, v) { 1547 | return d.setUTCHours(v); 1548 | }, 1549 | h: function (d, v) { 1550 | return d.setUTCHours(v); 1551 | }, 1552 | HH: function (d, v) { 1553 | return d.setUTCHours(v == 12 ? 0 : v); 1554 | }, 1555 | H: function (d, v) { 1556 | return d.setUTCHours(v == 12 ? 0 : v); 1557 | }, 1558 | ii: function (d, v) { 1559 | return d.setUTCMinutes(v); 1560 | }, 1561 | i: function (d, v) { 1562 | return d.setUTCMinutes(v); 1563 | }, 1564 | ss: function (d, v) { 1565 | return d.setUTCSeconds(v); 1566 | }, 1567 | s: function (d, v) { 1568 | return d.setUTCSeconds(v); 1569 | }, 1570 | yyyy: function (d, v) { 1571 | return d.setUTCFullYear(v); 1572 | }, 1573 | yy: function (d, v) { 1574 | return d.setUTCFullYear(2000 + v); 1575 | }, 1576 | m: function (d, v) { 1577 | v -= 1; 1578 | while (v < 0) v += 12; 1579 | v %= 12; 1580 | d.setUTCMonth(v); 1581 | while (d.getUTCMonth() != v) 1582 | if (isNaN(d.getUTCMonth())) 1583 | return d; 1584 | else 1585 | d.setUTCDate(d.getUTCDate() - 1); 1586 | return d; 1587 | }, 1588 | d: function (d, v) { 1589 | return d.setUTCDate(v); 1590 | }, 1591 | p: function (d, v) { 1592 | return d.setUTCHours(v == 1 ? d.getUTCHours() + 12 : d.getUTCHours()); 1593 | } 1594 | }, 1595 | val, filtered, part; 1596 | setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m']; 1597 | setters_map['dd'] = setters_map['d']; 1598 | setters_map['P'] = setters_map['p']; 1599 | date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds()); 1600 | if (parts.length == format.parts.length) { 1601 | for (var i = 0, cnt = format.parts.length; i < cnt; i++) { 1602 | val = parseInt(parts[i], 10); 1603 | part = format.parts[i]; 1604 | if (isNaN(val)) { 1605 | switch (part) { 1606 | case 'MM': 1607 | filtered = $(dates[language].months).filter(function () { 1608 | var m = this.slice(0, parts[i].length), 1609 | p = parts[i].slice(0, m.length); 1610 | return m == p; 1611 | }); 1612 | val = $.inArray(filtered[0], dates[language].months) + 1; 1613 | break; 1614 | case 'M': 1615 | filtered = $(dates[language].monthsShort).filter(function () { 1616 | var m = this.slice(0, parts[i].length), 1617 | p = parts[i].slice(0, m.length); 1618 | return m.toLowerCase() == p.toLowerCase(); 1619 | }); 1620 | val = $.inArray(filtered[0], dates[language].monthsShort) + 1; 1621 | break; 1622 | case 'p': 1623 | case 'P': 1624 | val = $.inArray(parts[i].toLowerCase(), dates[language].meridiem); 1625 | break; 1626 | } 1627 | } 1628 | parsed[part] = val; 1629 | } 1630 | for (var i = 0, s; i < setters_order.length; i++) { 1631 | s = setters_order[i]; 1632 | if (s in parsed && !isNaN(parsed[s])) 1633 | setters_map[s](date, parsed[s]) 1634 | } 1635 | } 1636 | return date; 1637 | }, 1638 | formatDate: function (date, format, language, type) { 1639 | if (date == null) { 1640 | return ''; 1641 | } 1642 | var val; 1643 | if (type == 'standard') { 1644 | val = { 1645 | t: date.getTime(), 1646 | // year 1647 | yy: date.getUTCFullYear().toString().substring(2), 1648 | yyyy: date.getUTCFullYear(), 1649 | // month 1650 | m: date.getUTCMonth() + 1, 1651 | M: dates[language].monthsShort[date.getUTCMonth()], 1652 | MM: dates[language].months[date.getUTCMonth()], 1653 | // day 1654 | d: date.getUTCDate(), 1655 | D: dates[language].daysShort[date.getUTCDay()], 1656 | DD: dates[language].days[date.getUTCDay()], 1657 | p: (dates[language].meridiem.length == 2 ? dates[language].meridiem[date.getUTCHours() < 12 ? 0 : 1] : ''), 1658 | // hour 1659 | h: date.getUTCHours(), 1660 | // minute 1661 | i: date.getUTCMinutes(), 1662 | // second 1663 | s: date.getUTCSeconds() 1664 | }; 1665 | 1666 | if (dates[language].meridiem.length == 2) { 1667 | val.H = (val.h % 12 == 0 ? 12 : val.h % 12); 1668 | } 1669 | else { 1670 | val.H = val.h; 1671 | } 1672 | val.HH = (val.H < 10 ? '0' : '') + val.H; 1673 | val.P = val.p.toUpperCase(); 1674 | val.hh = (val.h < 10 ? '0' : '') + val.h; 1675 | val.ii = (val.i < 10 ? '0' : '') + val.i; 1676 | val.ss = (val.s < 10 ? '0' : '') + val.s; 1677 | val.dd = (val.d < 10 ? '0' : '') + val.d; 1678 | val.mm = (val.m < 10 ? '0' : '') + val.m; 1679 | } else if (type == 'php') { 1680 | // php format 1681 | val = { 1682 | // year 1683 | y: date.getUTCFullYear().toString().substring(2), 1684 | Y: date.getUTCFullYear(), 1685 | // month 1686 | F: dates[language].months[date.getUTCMonth()], 1687 | M: dates[language].monthsShort[date.getUTCMonth()], 1688 | n: date.getUTCMonth() + 1, 1689 | t: DPGlobal.getDaysInMonth(date.getUTCFullYear(), date.getUTCMonth()), 1690 | // day 1691 | j: date.getUTCDate(), 1692 | l: dates[language].days[date.getUTCDay()], 1693 | D: dates[language].daysShort[date.getUTCDay()], 1694 | w: date.getUTCDay(), // 0 -> 6 1695 | N: (date.getUTCDay() == 0 ? 7 : date.getUTCDay()), // 1 -> 7 1696 | S: (date.getUTCDate() % 10 <= dates[language].suffix.length ? dates[language].suffix[date.getUTCDate() % 10 - 1] : ''), 1697 | // hour 1698 | a: (dates[language].meridiem.length == 2 ? dates[language].meridiem[date.getUTCHours() < 12 ? 0 : 1] : ''), 1699 | g: (date.getUTCHours() % 12 == 0 ? 12 : date.getUTCHours() % 12), 1700 | G: date.getUTCHours(), 1701 | // minute 1702 | i: date.getUTCMinutes(), 1703 | // second 1704 | s: date.getUTCSeconds() 1705 | }; 1706 | val.m = (val.n < 10 ? '0' : '') + val.n; 1707 | val.d = (val.j < 10 ? '0' : '') + val.j; 1708 | val.A = val.a.toString().toUpperCase(); 1709 | val.h = (val.g < 10 ? '0' : '') + val.g; 1710 | val.H = (val.G < 10 ? '0' : '') + val.G; 1711 | val.i = (val.i < 10 ? '0' : '') + val.i; 1712 | val.s = (val.s < 10 ? '0' : '') + val.s; 1713 | } else { 1714 | throw new Error('Invalid format type.'); 1715 | } 1716 | var date = [], 1717 | seps = $.extend([], format.separators); 1718 | for (var i = 0, cnt = format.parts.length; i < cnt; i++) { 1719 | if (seps.length) { 1720 | date.push(seps.shift()); 1721 | } 1722 | date.push(val[format.parts[i]]); 1723 | } 1724 | if (seps.length) { 1725 | date.push(seps.shift()); 1726 | } 1727 | return date.join(''); 1728 | }, 1729 | convertViewMode: function (viewMode) { 1730 | switch (viewMode) { 1731 | case 4: 1732 | case 'decade': 1733 | viewMode = 4; 1734 | break; 1735 | case 3: 1736 | case 'year': 1737 | viewMode = 3; 1738 | break; 1739 | case 2: 1740 | case 'month': 1741 | viewMode = 2; 1742 | break; 1743 | case 1: 1744 | case 'day': 1745 | viewMode = 1; 1746 | break; 1747 | case 0: 1748 | case 'hour': 1749 | viewMode = 0; 1750 | break; 1751 | } 1752 | 1753 | return viewMode; 1754 | }, 1755 | headTemplate: '' + 1756 | '' + 1757 | '' + 1758 | '' + 1759 | '' + 1760 | '' + 1761 | '', 1762 | headTemplateV3: '' + 1763 | '' + 1764 | ' ' + 1765 | '' + 1766 | ' ' + 1767 | '' + 1768 | '', 1769 | contTemplate: '', 1770 | footTemplate: '' + 1771 | '' + 1772 | '' + 1773 | '' 1774 | }; 1775 | DPGlobal.template = '
' + 1776 | '
' + 1777 | '' + 1778 | DPGlobal.headTemplate + 1779 | DPGlobal.contTemplate + 1780 | DPGlobal.footTemplate + 1781 | '
' + 1782 | '
' + 1783 | '
' + 1784 | '' + 1785 | DPGlobal.headTemplate + 1786 | DPGlobal.contTemplate + 1787 | DPGlobal.footTemplate + 1788 | '
' + 1789 | '
' + 1790 | '
' + 1791 | '' + 1792 | DPGlobal.headTemplate + 1793 | '' + 1794 | DPGlobal.footTemplate + 1795 | '
' + 1796 | '
' + 1797 | '
' + 1798 | '' + 1799 | DPGlobal.headTemplate + 1800 | DPGlobal.contTemplate + 1801 | DPGlobal.footTemplate + 1802 | '
' + 1803 | '
' + 1804 | '
' + 1805 | '' + 1806 | DPGlobal.headTemplate + 1807 | DPGlobal.contTemplate + 1808 | DPGlobal.footTemplate + 1809 | '
' + 1810 | '
' + 1811 | '
'; 1812 | DPGlobal.templateV3 = '
' + 1813 | '
' + 1814 | '' + 1815 | DPGlobal.headTemplateV3 + 1816 | DPGlobal.contTemplate + 1817 | DPGlobal.footTemplate + 1818 | '
' + 1819 | '
' + 1820 | '
' + 1821 | '' + 1822 | DPGlobal.headTemplateV3 + 1823 | DPGlobal.contTemplate + 1824 | DPGlobal.footTemplate + 1825 | '
' + 1826 | '
' + 1827 | '
' + 1828 | '' + 1829 | DPGlobal.headTemplateV3 + 1830 | '' + 1831 | DPGlobal.footTemplate + 1832 | '
' + 1833 | '
' + 1834 | '
' + 1835 | '' + 1836 | DPGlobal.headTemplateV3 + 1837 | DPGlobal.contTemplate + 1838 | DPGlobal.footTemplate + 1839 | '
' + 1840 | '
' + 1841 | '
' + 1842 | '' + 1843 | DPGlobal.headTemplateV3 + 1844 | DPGlobal.contTemplate + 1845 | DPGlobal.footTemplate + 1846 | '
' + 1847 | '
' + 1848 | '
'; 1849 | $.fn.datetimepicker.DPGlobal = DPGlobal; 1850 | 1851 | /* DATETIMEPICKER NO CONFLICT 1852 | * =================== */ 1853 | 1854 | $.fn.datetimepicker.noConflict = function () { 1855 | $.fn.datetimepicker = old; 1856 | return this; 1857 | }; 1858 | 1859 | /* DATETIMEPICKER DATA-API 1860 | * ================== */ 1861 | 1862 | $(document).on( 1863 | 'focus.datetimepicker.data-api click.datetimepicker.data-api', 1864 | '[data-provide="datetimepicker"]', 1865 | function (e) { 1866 | var $this = $(this); 1867 | if ($this.data('datetimepicker')) return; 1868 | e.preventDefault(); 1869 | // component click requires us to explicitly show it 1870 | $this.datetimepicker('show'); 1871 | } 1872 | ); 1873 | $(function () { 1874 | $('[data-provide="datetimepicker-inline"]').datetimepicker(); 1875 | }); 1876 | 1877 | })); 1878 | --------------------------------------------------------------------------------