diff --git a/backend/controllers/SiteController.php b/backend/controllers/SiteController.php index 6d0a165..19fb2a2 100755 --- a/backend/controllers/SiteController.php +++ b/backend/controllers/SiteController.php @@ -6,6 +6,7 @@ use yii\web\Controller; use yii\filters\VerbFilter; use yii\filters\AccessControl; use common\models\LoginForm; +use yii\helpers\Url; /** * Site controller @@ -18,20 +19,6 @@ class SiteController extends Controller public function behaviors() { return [ - 'access' => [ - 'class' => AccessControl::className(), - 'rules' => [ - [ - 'actions' => ['login', 'error'], - 'allow' => true, - ], - [ - 'actions' => ['logout', 'index'], - 'allow' => true, - 'roles' => ['@'], - ], - ], - ], 'verbs' => [ 'class' => VerbFilter::className(), 'actions' => [ @@ -76,10 +63,14 @@ class SiteController extends Controller $model = new LoginForm(); if ($model->load(Yii::$app->request->post()) && $model->login()) { - return $this->goBack(); + if (\Yii::$app->user->can('secure')) { + return $this->goBack(); + } else { + Yii::$app->user->logout(); + return $this->redirect(Url::to('/card/user-card')); + } } else { $model->password = ''; - return $this->render('login', [ 'model' => $model, ]); diff --git a/backend/modules/accesses/controllers/AccessesController.php b/backend/modules/accesses/controllers/AccessesController.php index 966c5c5..513f530 100755 --- a/backend/modules/accesses/controllers/AccessesController.php +++ b/backend/modules/accesses/controllers/AccessesController.php @@ -8,6 +8,7 @@ use common\models\UserCardAccesses; use Yii; use common\models\Accesses; use app\modules\accesses\models\AccessesSearch; +use yii\filters\AccessControl; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; @@ -29,6 +30,15 @@ class AccessesController extends Controller 'delete' => ['POST'], ], ], + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'allow' => true, + 'roles' => ['admin'], + ], + ], + ], ]; } diff --git a/backend/modules/balance/controllers/BalanceController.php b/backend/modules/balance/controllers/BalanceController.php index ed760b5..56332c7 100755 --- a/backend/modules/balance/controllers/BalanceController.php +++ b/backend/modules/balance/controllers/BalanceController.php @@ -10,12 +10,39 @@ use common\models\FieldsValueNew; use DateTime; use Yii; use yii\data\ActiveDataProvider; +use yii\filters\AccessControl; +use yii\filters\VerbFilter; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\db\Query; class BalanceController extends Controller { + /** + * {@inheritdoc} + */ + public function behaviors() + { + return [ + 'verbs' => [ + 'class' => VerbFilter::className(), + 'actions' => [ + 'delete' => ['POST'], + ], + ], + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'allow' => true, + 'roles' => ['admin'], + ], + ], + ], + ]; + } + + public function actionIndex() { $searchModel = new BalanceSearch(); diff --git a/backend/modules/calendar/controllers/CalendarController.php b/backend/modules/calendar/controllers/CalendarController.php index 99dbbaf..86ae7e3 100644 --- a/backend/modules/calendar/controllers/CalendarController.php +++ b/backend/modules/calendar/controllers/CalendarController.php @@ -6,6 +6,8 @@ use backend\modules\card\models\UserCardSearch; use common\classes\Debug; use Yii; use yii\data\ArrayDataProvider; +use yii\filters\AccessControl; +use yii\filters\VerbFilter; use yii\web\Controller; /** @@ -13,6 +15,30 @@ use yii\web\Controller; */ class CalendarController extends Controller { + /** + * {@inheritdoc} + */ + public function behaviors() + { + return [ + 'verbs' => [ + 'class' => VerbFilter::className(), + 'actions' => [ + 'delete' => ['POST'], + ], + ], + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'allow' => true, + 'roles' => ['admin'], + ], + ], + ], + ]; + } + /** * Renders the index view for the module * @return string diff --git a/backend/modules/card/controllers/UserCardController.php b/backend/modules/card/controllers/UserCardController.php index a8b3909..e748257 100755 --- a/backend/modules/card/controllers/UserCardController.php +++ b/backend/modules/card/controllers/UserCardController.php @@ -14,6 +14,7 @@ use backend\modules\card\models\UserCard; use backend\modules\card\models\UserCardSearch; use yii\data\ActiveDataProvider; use yii\db\Expression; +use yii\filters\AccessControl; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; @@ -35,6 +36,15 @@ class UserCardController extends Controller 'delete' => ['POST'], ], ], + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'allow' => true, + 'roles' => ['admin'], + ], + ], + ], ]; } diff --git a/backend/modules/company/controllers/CompanyController.php b/backend/modules/company/controllers/CompanyController.php index 2ee9ff7..62587e7 100755 --- a/backend/modules/company/controllers/CompanyController.php +++ b/backend/modules/company/controllers/CompanyController.php @@ -7,6 +7,7 @@ use Yii; use backend\modules\company\models\Company; use backend\modules\company\models\CompanySearch; use yii\data\ActiveDataProvider; +use yii\filters\AccessControl; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; @@ -28,6 +29,15 @@ class CompanyController extends Controller 'delete' => ['POST'], ], ], + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'allow' => true, + 'roles' => ['admin'], + ], + ], + ], ]; } diff --git a/backend/modules/hh/controllers/HhController.php b/backend/modules/hh/controllers/HhController.php index 971fd5e..e3d90fb 100755 --- a/backend/modules/hh/controllers/HhController.php +++ b/backend/modules/hh/controllers/HhController.php @@ -9,6 +9,7 @@ use Yii; use backend\modules\hh\models\Hh; use backend\modules\hh\models\HhSearch; use yii\data\ActiveDataProvider; +use yii\filters\AccessControl; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; @@ -30,6 +31,15 @@ class HhController extends Controller 'delete' => ['POST'], ], ], + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'allow' => true, + 'roles' => ['admin'], + ], + ], + ], ]; } diff --git a/backend/modules/hh/controllers/HhJobController.php b/backend/modules/hh/controllers/HhJobController.php index be5cf6e..92d065d 100755 --- a/backend/modules/hh/controllers/HhJobController.php +++ b/backend/modules/hh/controllers/HhJobController.php @@ -6,6 +6,7 @@ use common\models\Hh; use Yii; use backend\modules\hh\models\HhJob; use backend\modules\hh\models\HhJobSearch; +use yii\filters\AccessControl; use yii\helpers\ArrayHelper; use yii\web\Controller; use yii\web\NotFoundHttpException; @@ -28,6 +29,15 @@ class HhJobController extends Controller 'delete' => ['POST'], ], ], + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'allow' => true, + 'roles' => ['admin'], + ], + ], + ], ]; } diff --git a/backend/modules/holiday/controllers/HolidayController.php b/backend/modules/holiday/controllers/HolidayController.php index a2afc39..908a606 100755 --- a/backend/modules/holiday/controllers/HolidayController.php +++ b/backend/modules/holiday/controllers/HolidayController.php @@ -6,6 +6,7 @@ use backend\modules\holiday\models\Holiday; use backend\modules\holiday\models\HolidaySearch; use common\classes\Debug; use Yii; +use yii\filters\AccessControl; use yii\filters\VerbFilter; use yii\web\Controller; use yii\web\NotFoundHttpException; @@ -24,6 +25,15 @@ class HolidayController extends Controller 'delete' => ['POST'], ], ], + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'allow' => true, + 'roles' => ['admin'], + ], + ], + ], ]; } diff --git a/backend/modules/notes/controllers/NotesController.php b/backend/modules/notes/controllers/NotesController.php index d42033e..74bff12 100755 --- a/backend/modules/notes/controllers/NotesController.php +++ b/backend/modules/notes/controllers/NotesController.php @@ -7,6 +7,8 @@ use Yii; use backend\modules\notes\models\Note; use common\models\FieldsValueNew; use yii\data\ActiveDataProvider; +use yii\filters\AccessControl; +use yii\filters\VerbFilter; use yii\web\Controller; use yii\web\NotFoundHttpException; @@ -15,6 +17,30 @@ use yii\web\NotFoundHttpException; */ class NotesController extends Controller { + /** + * {@inheritdoc} + */ + public function behaviors() + { + return [ + 'verbs' => [ + 'class' => VerbFilter::className(), + 'actions' => [ + 'delete' => ['POST'], + ], + ], + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'allow' => true, + 'roles' => ['admin'], + ], + ], + ], + ]; + } + /** * Renders the index view for the module * @return string diff --git a/backend/modules/project/controllers/ProjectController.php b/backend/modules/project/controllers/ProjectController.php index 1db532d..1cdd5c7 100755 --- a/backend/modules/project/controllers/ProjectController.php +++ b/backend/modules/project/controllers/ProjectController.php @@ -11,6 +11,7 @@ use Yii; use backend\modules\project\models\Project; use backend\modules\project\models\ProjectSearch; use yii\data\ActiveDataProvider; +use yii\filters\AccessControl; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; @@ -32,6 +33,15 @@ class ProjectController extends Controller 'delete' => ['POST'], ], ], + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'allow' => true, + 'roles' => ['admin'], + ], + ], + ], ]; } diff --git a/backend/modules/settings/controllers/AdditionalFieldsController.php b/backend/modules/settings/controllers/AdditionalFieldsController.php index aac3dce..526880e 100755 --- a/backend/modules/settings/controllers/AdditionalFieldsController.php +++ b/backend/modules/settings/controllers/AdditionalFieldsController.php @@ -7,6 +7,7 @@ use common\models\UseField; use Yii; use backend\modules\settings\models\AdditionalFields; use backend\modules\settings\models\AdditionalFieldsSearch; +use yii\filters\AccessControl; use yii\helpers\ArrayHelper; use yii\web\Controller; use yii\web\NotFoundHttpException; @@ -29,6 +30,15 @@ class AdditionalFieldsController extends Controller 'delete' => ['POST'], ], ], + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'allow' => true, + 'roles' => ['admin'], + ], + ], + ], ]; } diff --git a/backend/modules/settings/controllers/PositionController.php b/backend/modules/settings/controllers/PositionController.php index 0582978..c1093f6 100755 --- a/backend/modules/settings/controllers/PositionController.php +++ b/backend/modules/settings/controllers/PositionController.php @@ -5,6 +5,7 @@ namespace backend\modules\settings\controllers; use Yii; use backend\modules\settings\models\Position; use backend\modules\settings\models\PositionSearch; +use yii\filters\AccessControl; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; @@ -26,6 +27,15 @@ class PositionController extends Controller 'delete' => ['POST'], ], ], + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'allow' => true, + 'roles' => ['admin'], + ], + ], + ], ]; } diff --git a/backend/modules/settings/controllers/SkillController.php b/backend/modules/settings/controllers/SkillController.php index 5bced31..3786a54 100755 --- a/backend/modules/settings/controllers/SkillController.php +++ b/backend/modules/settings/controllers/SkillController.php @@ -5,6 +5,7 @@ namespace backend\modules\settings\controllers; use Yii; use backend\modules\settings\models\Skill; use backend\modules\settings\models\SkillSearch; +use yii\filters\AccessControl; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; @@ -26,6 +27,15 @@ class SkillController extends Controller 'delete' => ['POST'], ], ], + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'allow' => true, + 'roles' => ['admin'], + ], + ], + ], ]; } diff --git a/backend/modules/settings/controllers/StatusController.php b/backend/modules/settings/controllers/StatusController.php index f2da4c4..f184091 100755 --- a/backend/modules/settings/controllers/StatusController.php +++ b/backend/modules/settings/controllers/StatusController.php @@ -6,6 +6,7 @@ use common\models\UseStatus; use Yii; use backend\modules\settings\models\Status; use backend\modules\settings\models\StatusSearch; +use yii\filters\AccessControl; use yii\helpers\ArrayHelper; use yii\web\Controller; use yii\web\NotFoundHttpException; @@ -28,6 +29,15 @@ class StatusController extends Controller 'delete' => ['POST'], ], ], + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'allow' => true, + 'roles' => ['admin'], + ], + ], + ], ]; } diff --git a/common/config/main.php b/common/config/main.php index 88ac995..0cf53ca 100755 --- a/common/config/main.php +++ b/common/config/main.php @@ -9,6 +9,12 @@ return [ 'cache' => [ 'class' => 'yii\caching\FileCache', ], + 'authManager' => [ + 'class' => 'yii\rbac\DbManager', +// 'itemFile' => '@common/components/rbac/items.php', +// 'assignmentFile' => '@common/components/rbac/assignments.php', +// 'ruleFile' => '@common/components/rbac/rules.php' + ], ], 'controllerMap' => [ 'elfinder' => [ diff --git a/common/models/AuthAssignment.php b/common/models/AuthAssignment.php new file mode 100644 index 0000000..2f61253 --- /dev/null +++ b/common/models/AuthAssignment.php @@ -0,0 +1,59 @@ + 64], + [['item_name', 'user_id'], 'unique', 'targetAttribute' => ['item_name', 'user_id']], + [['item_name'], 'exist', 'skipOnError' => true, 'targetClass' => AuthItem::className(), 'targetAttribute' => ['item_name' => 'name']], + ]; + } + + /** + * {@inheritdoc} + */ + public function attributeLabels() + { + return [ + 'item_name' => 'Item Name', + 'user_id' => 'User ID', + 'created_at' => 'Created At', + ]; + } + + /** + * @return \yii\db\ActiveQuery + */ + public function getItemName() + { + return $this->hasOne(AuthItem::className(), ['name' => 'item_name']); + } +} diff --git a/common/models/UserCard.php b/common/models/UserCard.php index d2f3555..abc862c 100755 --- a/common/models/UserCard.php +++ b/common/models/UserCard.php @@ -6,6 +6,7 @@ use common\classes\Debug; use Yii; use yii\behaviors\TimestampBehavior; use yii\db\Expression; +use yii\filters\AccessControl; use yii\helpers\ArrayHelper; /** diff --git a/composer.json b/composer.json index be9888b..4cc4779 100755 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "nkovacs/yii2-datetimepicker": "*", "mirocow/yii2-eav": "*", "kartik-v/yii2-widget-fileinput": "^1.0", - "2amigos/yii2-file-upload-widget": "~1.0" + "2amigos/yii2-file-upload-widget": "~1.0", }, "require-dev": { "yiisoft/yii2-debug": "~2.0.0", diff --git a/console/config/main.php b/console/config/main.php index a67be5c..16d8dcf 100755 --- a/console/config/main.php +++ b/console/config/main.php @@ -22,6 +22,12 @@ return [ ], ], 'components' => [ + 'user' => [ + 'identityClass' => 'common\models\User', + 'class' => 'yii\web\User', + 'enableSession' => false, + 'enableAutoLogin' => false, + ], 'log' => [ 'targets' => [ [ diff --git a/console/controllers/RbacController.php b/console/controllers/RbacController.php new file mode 100644 index 0000000..3e0fe75 --- /dev/null +++ b/console/controllers/RbacController.php @@ -0,0 +1,36 @@ +authManager; + + $secure = $auth->createPermission('secure'); + $secure->description = 'Admin panel'; + $auth->add($secure); + + $front = $auth->createPermission('front'); + $front->description = 'Frontend'; + $auth->add($front); + + $user = $auth->createRole('user'); + $auth->add($user); + $auth->addChild($user, $front); + + $admin = $auth->createRole('admin'); + $auth->add($admin); + $auth->addChild($admin, $secure); + $auth->addChild($admin, $user); + + $auth->assign($user, 2); + $auth->assign($admin, 1); + } +} \ No newline at end of file diff --git a/frontend/models/SignupForm.php b/frontend/models/SignupForm.php index a66a875..5eec1cc 100755 --- a/frontend/models/SignupForm.php +++ b/frontend/models/SignupForm.php @@ -1,6 +1,8 @@ email = $this->email; $user->setPassword($this->password); $user->generateAuthKey(); + $user->save(); + + $auth = Yii::$app->authManager; + $authorRole = $auth->getRole('user'); + $auth->assign($authorRole, $user->id); return $user->save() ? $user : null; } diff --git a/node_modules/sticky-table-headers/composer.json b/node_modules/sticky-table-headers/composer.json new file mode 100644 index 0000000..390ac5a --- /dev/null +++ b/node_modules/sticky-table-headers/composer.json @@ -0,0 +1,20 @@ +{ + "name": "jmosbech/sticky-table-headers", + "description": "jQuery sticky table headers plugin", + "keywords": [ + "jquery", + "sticky", + "table", + "headers" + ], + "homepage": "https://github.com/jmosbech/StickyTableHeaders", + "authors": [ + { + "name": "Jonas Mosbech" + } + ], + "support": { + "issues": "https://github.com/jmosbech/StickyTableHeaders/issues" + }, + "license": "MIT" +} diff --git a/node_modules/sticky-table-headers/js/jquery.stickytableheaders.js b/node_modules/sticky-table-headers/js/jquery.stickytableheaders.js new file mode 100644 index 0000000..4d742f1 --- /dev/null +++ b/node_modules/sticky-table-headers/js/jquery.stickytableheaders.js @@ -0,0 +1,325 @@ +/*! Copyright (c) Jonas Mosbech - https://github.com/jmosbech/StickyTableHeaders + MIT license info: https://github.com/jmosbech/StickyTableHeaders/blob/master/license.txt */ + +;(function ($, window, undefined) { + 'use strict'; + + var name = 'stickyTableHeaders', + id = 0, + defaults = { + fixedOffset: 0, + leftOffset: 0, + marginTop: 0, + objDocument: document, + objHead: 'head', + objWindow: window, + scrollableArea: window, + cacheHeaderHeight: false, + zIndex: 3 + }; + + function Plugin (el, options) { + // To avoid scope issues, use 'base' instead of 'this' + // to reference this class from internal events and functions. + var base = this; + + // Access to jQuery and DOM versions of element + base.$el = $(el); + base.el = el; + base.id = id++; + + // Listen for destroyed, call teardown + base.$el.bind('destroyed', + $.proxy(base.teardown, base)); + + // Cache DOM refs for performance reasons + base.$clonedHeader = null; + base.$originalHeader = null; + + // Cache header height for performance reasons + base.cachedHeaderHeight = null; + + // Keep track of state + base.isSticky = false; + base.hasBeenSticky = false; + base.leftOffset = null; + base.topOffset = null; + + base.init = function () { + base.setOptions(options); + + base.$el.each(function () { + var $this = $(this); + + // remove padding on to fix issue #7 + $this.css('padding', 0); + + base.$originalHeader = $('thead:first', this); + base.$clonedHeader = base.$originalHeader.clone(); + $this.trigger('clonedHeader.' + name, [base.$clonedHeader]); + + base.$clonedHeader.addClass('tableFloatingHeader'); + base.$clonedHeader.css({display: 'none', opacity: 0.0}); + + base.$originalHeader.addClass('tableFloatingHeaderOriginal'); + + base.$originalHeader.after(base.$clonedHeader); + + base.$printStyle = $(''); + base.$head.append(base.$printStyle); + }); + + base.$clonedHeader.find("input, select").attr("disabled", true); + + base.updateWidth(); + base.toggleHeaders(); + base.bind(); + }; + + base.destroy = function (){ + base.$el.unbind('destroyed', base.teardown); + base.teardown(); + }; + + base.teardown = function(){ + if (base.isSticky) { + base.$originalHeader.css('position', 'static'); + } + $.removeData(base.el, 'plugin_' + name); + base.unbind(); + + base.$clonedHeader.remove(); + base.$originalHeader.removeClass('tableFloatingHeaderOriginal'); + base.$originalHeader.css('visibility', 'visible'); + base.$printStyle.remove(); + + base.el = null; + base.$el = null; + }; + + base.bind = function(){ + base.$scrollableArea.on('scroll.' + name, base.toggleHeaders); + if (!base.isWindowScrolling) { + base.$window.on('scroll.' + name + base.id, base.setPositionValues); + base.$window.on('resize.' + name + base.id, base.toggleHeaders); + } + base.$scrollableArea.on('resize.' + name, base.toggleHeaders); + base.$scrollableArea.on('resize.' + name, base.updateWidth); + }; + + base.unbind = function(){ + // unbind window events by specifying handle so we don't remove too much + base.$scrollableArea.off('.' + name, base.toggleHeaders); + if (!base.isWindowScrolling) { + base.$window.off('.' + name + base.id, base.setPositionValues); + base.$window.off('.' + name + base.id, base.toggleHeaders); + } + base.$scrollableArea.off('.' + name, base.updateWidth); + }; + + // We debounce the functions bound to the scroll and resize events + base.debounce = function (fn, delay) { + var timer = null; + return function () { + var context = this, args = arguments; + clearTimeout(timer); + timer = setTimeout(function () { + fn.apply(context, args); + }, delay); + }; + }; + + base.toggleHeaders = base.debounce(function () { + if (base.$el) { + base.$el.each(function () { + var $this = $(this), + newLeft, + newTopOffset = base.isWindowScrolling ? ( + isNaN(base.options.fixedOffset) ? + base.options.fixedOffset.outerHeight() : + base.options.fixedOffset + ) : + base.$scrollableArea.offset().top + (!isNaN(base.options.fixedOffset) ? base.options.fixedOffset : 0), + offset = $this.offset(), + + scrollTop = base.$scrollableArea.scrollTop() + newTopOffset, + scrollLeft = base.$scrollableArea.scrollLeft(), + + headerHeight, + + scrolledPastTop = base.isWindowScrolling ? + scrollTop > offset.top : + newTopOffset > offset.top, + notScrolledPastBottom; + + if (scrolledPastTop) { + headerHeight = base.options.cacheHeaderHeight ? base.cachedHeaderHeight : base.$clonedHeader.height(); + notScrolledPastBottom = (base.isWindowScrolling ? scrollTop : 0) < + (offset.top + $this.height() - headerHeight - (base.isWindowScrolling ? 0 : newTopOffset)); + } + + if (scrolledPastTop && notScrolledPastBottom) { + newLeft = offset.left - scrollLeft + base.options.leftOffset; + base.$originalHeader.css({ + 'position': 'fixed', + 'margin-top': base.options.marginTop, + 'top': 0, + 'left': newLeft, + 'z-index': base.options.zIndex + }); + base.leftOffset = newLeft; + base.topOffset = newTopOffset; + base.$clonedHeader.css('display', ''); + if (!base.isSticky) { + base.isSticky = true; + // make sure the width is correct: the user might have resized the browser while in static mode + base.updateWidth(); + $this.trigger('enabledStickiness.' + name); + } + base.setPositionValues(); + } else if (base.isSticky) { + base.$originalHeader.css('position', 'static'); + base.$clonedHeader.css('display', 'none'); + base.isSticky = false; + base.resetWidth($('td,th', base.$clonedHeader), $('td,th', base.$originalHeader)); + $this.trigger('disabledStickiness.' + name); + } + }); + } + }, 0); + + base.setPositionValues = base.debounce(function () { + var winScrollTop = base.$window.scrollTop(), + winScrollLeft = base.$window.scrollLeft(); + if (!base.isSticky || + winScrollTop < 0 || winScrollTop + base.$window.height() > base.$document.height() || + winScrollLeft < 0 || winScrollLeft + base.$window.width() > base.$document.width()) { + return; + } + base.$originalHeader.css({ + 'top': base.topOffset - (base.isWindowScrolling ? 0 : winScrollTop), + 'left': base.leftOffset - (base.isWindowScrolling ? 0 : winScrollLeft) + }); + }, 0); + + base.updateWidth = base.debounce(function () { + if (!base.isSticky) { + return; + } + // Copy cell widths from clone + if (!base.$originalHeaderCells) { + base.$originalHeaderCells = $('th,td', base.$originalHeader); + } + if (!base.$clonedHeaderCells) { + base.$clonedHeaderCells = $('th,td', base.$clonedHeader); + } + var cellWidths = base.getWidth(base.$clonedHeaderCells); + base.setWidth(cellWidths, base.$clonedHeaderCells, base.$originalHeaderCells); + + // Copy row width from whole table + base.$originalHeader.css('width', base.$clonedHeader.width()); + + // If we're caching the height, we need to update the cached value when the width changes + if (base.options.cacheHeaderHeight) { + base.cachedHeaderHeight = base.$clonedHeader.height(); + } + }, 0); + + base.getWidth = function ($clonedHeaders) { + var widths = []; + $clonedHeaders.each(function (index) { + var width, $this = $(this); + + if ($this.css('box-sizing') === 'border-box') { + var boundingClientRect = $this[0].getBoundingClientRect(); + if(boundingClientRect.width) { + width = boundingClientRect.width; // #39: border-box bug + } else { + width = boundingClientRect.right - boundingClientRect.left; // ie8 bug: getBoundingClientRect() does not have a width property + } + } else { + var $origTh = $('th', base.$originalHeader); + if ($origTh.css('border-collapse') === 'collapse') { + if (window.getComputedStyle) { + width = parseFloat(window.getComputedStyle(this, null).width); + } else { + // ie8 only + var leftPadding = parseFloat($this.css('padding-left')); + var rightPadding = parseFloat($this.css('padding-right')); + // Needs more investigation - this is assuming constant border around this cell and it's neighbours. + var border = parseFloat($this.css('border-width')); + width = $this.outerWidth() - leftPadding - rightPadding - border; + } + } else { + width = $this.width(); + } + } + + widths[index] = width; + }); + return widths; + }; + + base.setWidth = function (widths, $clonedHeaders, $origHeaders) { + $clonedHeaders.each(function (index) { + var width = widths[index]; + $origHeaders.eq(index).css({ + 'min-width': width, + 'max-width': width + }); + }); + }; + + base.resetWidth = function ($clonedHeaders, $origHeaders) { + $clonedHeaders.each(function (index) { + var $this = $(this); + $origHeaders.eq(index).css({ + 'min-width': $this.css('min-width'), + 'max-width': $this.css('max-width') + }); + }); + }; + + base.setOptions = function (options) { + base.options = $.extend({}, defaults, options); + base.$window = $(base.options.objWindow); + base.$head = $(base.options.objHead); + base.$document = $(base.options.objDocument); + base.$scrollableArea = $(base.options.scrollableArea); + base.isWindowScrolling = base.$scrollableArea[0] === base.$window[0]; + }; + + base.updateOptions = function (options) { + base.setOptions(options); + // scrollableArea might have changed + base.unbind(); + base.bind(); + base.updateWidth(); + base.toggleHeaders(); + }; + + // Run initializer + base.init(); + } + + // A plugin wrapper around the constructor, + // preventing against multiple instantiations + $.fn[name] = function ( options ) { + return this.each(function () { + var instance = $.data(this, 'plugin_' + name); + if (instance) { + if (typeof options === 'string') { + instance[options].apply(instance); + } else { + instance.updateOptions(options); + } + } else if(options !== 'destroy') { + $.data(this, 'plugin_' + name, new Plugin( this, options )); + } + }); + }; + +})(jQuery, window); diff --git a/node_modules/sticky-table-headers/js/jquery.stickytableheaders.min.js b/node_modules/sticky-table-headers/js/jquery.stickytableheaders.min.js new file mode 100644 index 0000000..6579c01 --- /dev/null +++ b/node_modules/sticky-table-headers/js/jquery.stickytableheaders.min.js @@ -0,0 +1,6 @@ +/*! + * StickyTableHeaders 0.1.24 (2018-01-14 23:29) + * MIT licensed + * Copyright (C) Jonas Mosbech - https://github.com/jmosbech/StickyTableHeaders + */ +!function(e,i,t){"use strict";var o="stickyTableHeaders",n=0,d={fixedOffset:0,leftOffset:0,marginTop:0,objDocument:document,objHead:"head",objWindow:i,scrollableArea:i,cacheHeaderHeight:!1,zIndex:3};e.fn[o]=function(t){return this.each(function(){var l=e.data(this,"plugin_"+o);l?"string"==typeof t?l[t].apply(l):l.updateOptions(t):"destroy"!==t&&e.data(this,"plugin_"+o,new function(t,l){var a=this;a.$el=e(t),a.el=t,a.id=n++,a.$el.bind("destroyed",e.proxy(a.teardown,a)),a.$clonedHeader=null,a.$originalHeader=null,a.cachedHeaderHeight=null,a.isSticky=!1,a.hasBeenSticky=!1,a.leftOffset=null,a.topOffset=null,a.init=function(){a.setOptions(l),a.$el.each(function(){var i=e(this);i.css("padding",0),a.$originalHeader=e("thead:first",this),a.$clonedHeader=a.$originalHeader.clone(),i.trigger("clonedHeader."+o,[a.$clonedHeader]),a.$clonedHeader.addClass("tableFloatingHeader"),a.$clonedHeader.css({display:"none",opacity:0}),a.$originalHeader.addClass("tableFloatingHeaderOriginal"),a.$originalHeader.after(a.$clonedHeader),a.$printStyle=e(''),a.$head.append(a.$printStyle)}),a.$clonedHeader.find("input, select").attr("disabled",!0),a.updateWidth(),a.toggleHeaders(),a.bind()},a.destroy=function(){a.$el.unbind("destroyed",a.teardown),a.teardown()},a.teardown=function(){a.isSticky&&a.$originalHeader.css("position","static"),e.removeData(a.el,"plugin_"+o),a.unbind(),a.$clonedHeader.remove(),a.$originalHeader.removeClass("tableFloatingHeaderOriginal"),a.$originalHeader.css("visibility","visible"),a.$printStyle.remove(),a.el=null,a.$el=null},a.bind=function(){a.$scrollableArea.on("scroll."+o,a.toggleHeaders),a.isWindowScrolling||(a.$window.on("scroll."+o+a.id,a.setPositionValues),a.$window.on("resize."+o+a.id,a.toggleHeaders)),a.$scrollableArea.on("resize."+o,a.toggleHeaders),a.$scrollableArea.on("resize."+o,a.updateWidth)},a.unbind=function(){a.$scrollableArea.off("."+o,a.toggleHeaders),a.isWindowScrolling||(a.$window.off("."+o+a.id,a.setPositionValues),a.$window.off("."+o+a.id,a.toggleHeaders)),a.$scrollableArea.off("."+o,a.updateWidth)},a.debounce=function(e,i){var t=null;return function(){var o=this,n=arguments;clearTimeout(t),t=setTimeout(function(){e.apply(o,n)},i)}},a.toggleHeaders=a.debounce(function(){a.$el&&a.$el.each(function(){var i,t,n,d=e(this),l=a.isWindowScrolling?isNaN(a.options.fixedOffset)?a.options.fixedOffset.outerHeight():a.options.fixedOffset:a.$scrollableArea.offset().top+(isNaN(a.options.fixedOffset)?0:a.options.fixedOffset),s=d.offset(),r=a.$scrollableArea.scrollTop()+l,c=a.$scrollableArea.scrollLeft(),f=a.isWindowScrolling?r>s.top:l>s.top;f&&(t=a.options.cacheHeaderHeight?a.cachedHeaderHeight:a.$clonedHeader.height(),n=(a.isWindowScrolling?r:0)a.$document.height()||i<0||i+a.$window.width()>a.$document.width()||a.$originalHeader.css({top:a.topOffset-(a.isWindowScrolling?0:e),left:a.leftOffset-(a.isWindowScrolling?0:i)})},0),a.updateWidth=a.debounce(function(){if(a.isSticky){a.$originalHeaderCells||(a.$originalHeaderCells=e("th,td",a.$originalHeader)),a.$clonedHeaderCells||(a.$clonedHeaderCells=e("th,td",a.$clonedHeader));var i=a.getWidth(a.$clonedHeaderCells);a.setWidth(i,a.$clonedHeaderCells,a.$originalHeaderCells),a.$originalHeader.css("width",a.$clonedHeader.width()),a.options.cacheHeaderHeight&&(a.cachedHeaderHeight=a.$clonedHeader.height())}},0),a.getWidth=function(t){var o=[];return t.each(function(t){var n,d=e(this);if("border-box"===d.css("box-sizing")){var l=d[0].getBoundingClientRect();n=l.width?l.width:l.right-l.left}else if("collapse"===e("th",a.$originalHeader).css("border-collapse"))if(i.getComputedStyle)n=parseFloat(i.getComputedStyle(this,null).width);else{var s=parseFloat(d.css("padding-left")),r=parseFloat(d.css("padding-right")),c=parseFloat(d.css("border-width"));n=d.outerWidth()-s-r-c}else n=d.width();o[t]=n}),o},a.setWidth=function(e,i,t){i.each(function(i){var o=e[i];t.eq(i).css({"min-width":o,"max-width":o})})},a.resetWidth=function(i,t){i.each(function(i){var o=e(this);t.eq(i).css({"min-width":o.css("min-width"),"max-width":o.css("max-width")})})},a.setOptions=function(i){a.options=e.extend({},d,i),a.$window=e(a.options.objWindow),a.$head=e(a.options.objHead),a.$document=e(a.options.objDocument),a.$scrollableArea=e(a.options.scrollableArea),a.isWindowScrolling=a.$scrollableArea[0]===a.$window[0]},a.updateOptions=function(e){a.setOptions(e),a.unbind(),a.bind(),a.updateWidth(),a.toggleHeaders()},a.init()}(this,t))})}}(jQuery,window); \ No newline at end of file diff --git a/node_modules/sticky-table-headers/license.txt b/node_modules/sticky-table-headers/license.txt new file mode 100644 index 0000000..a9da71c --- /dev/null +++ b/node_modules/sticky-table-headers/license.txt @@ -0,0 +1,20 @@ +Copyright (c) 2011 Jonas Mosbech + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/node_modules/sticky-table-headers/package.json b/node_modules/sticky-table-headers/package.json new file mode 100644 index 0000000..1c3e293 --- /dev/null +++ b/node_modules/sticky-table-headers/package.json @@ -0,0 +1,63 @@ +{ + "_from": "sticky-table-headers", + "_id": "sticky-table-headers@0.1.24", + "_inBundle": false, + "_integrity": "sha1-euofB5HKOgKwhgzYhe11ehrL84E=", + "_location": "/sticky-table-headers", + "_phantomChildren": {}, + "_requested": { + "type": "tag", + "registry": true, + "raw": "sticky-table-headers", + "name": "sticky-table-headers", + "escapedName": "sticky-table-headers", + "rawSpec": "", + "saveSpec": null, + "fetchSpec": "latest" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "https://registry.npmjs.org/sticky-table-headers/-/sticky-table-headers-0.1.24.tgz", + "_shasum": "7aea1f0791ca3a02b0860cd885ed757a1acbf381", + "_spec": "sticky-table-headers", + "_where": "/var/www/domains/guild", + "author": { + "name": "Jonas Mosbech", + "email": "https://github.com/jmosbech" + }, + "bugs": { + "url": "https://github.com/jmosbech/StickyTableHeaders/issues" + }, + "bundleDependencies": false, + "deprecated": false, + "description": "jQuery sticky table headers plugin", + "devDependencies": { + "grunt": "~1.0.1", + "grunt-bower-version": "^0.1.1", + "grunt-contrib-jshint": "~1.1.0", + "grunt-contrib-uglify": "~3.3.0" + }, + "homepage": "https://github.com/jmosbech/StickyTableHeaders#readme", + "keywords": [ + "jquery", + "sticky", + "table", + "headers", + "jquery-plugin", + "ecosystem:jquery" + ], + "license": "MIT", + "main": "js/jquery.stickytableheaders.js", + "name": "sticky-table-headers", + "repository": { + "type": "git", + "url": "git+https://github.com/jmosbech/StickyTableHeaders.git" + }, + "scripts": { + "build": "grunt" + }, + "unpkg": "js/jquery.stickytableheaders.min.js", + "version": "0.1.24" +} diff --git a/node_modules/sticky-table-headers/readme.md b/node_modules/sticky-table-headers/readme.md new file mode 100644 index 0000000..0a91908 --- /dev/null +++ b/node_modules/sticky-table-headers/readme.md @@ -0,0 +1,119 @@ +StickyTableHeaders +================== +So what's it good for? Well, let's say you want to display a long list of fairly uniform tabluar data, like [stock exchange listings](http://online.barrons.com/public/page/majormarket-nysecomposite-A.html) or [sport statistics](https://sports.yahoo.com/nfl/stats/weekly/?sortStatId=PASSING_YARDS&selectedTable=7) but you don't want your users to get lost in the data as they scroll down on the page. + +StickyTableHeaders to the rescue: By applying the StickyTableHeaders jQuery plugin to the table, the column headers will stick to the top of the viewport as you scroll down. + +Go ahead and [try out a demo](http://jsfiddle.net/jmosbech/stFcx/). + +The code is based on [this proof of concept](http://stackoverflow.com/questions/1030043/html-table-headers-always-visible-at-top-of-window-when-viewing-a-large-table/1041566#1041566). + +Installation +------------ +The best way to install is using [npm](https://www.npmjs.com/): + +```bash +npm install sticky-table-headers +``` + +or [Bower](http://bower.io/): + +```bash +bower install StickyTableHeaders +``` + +or by loading it directly from the [unpkg CDN](https://unpkg.com/sticky-table-headers): + +``` + +``` + +Usage +----- +Initializing the plugin is pretty straight forward: + +```js +$('table').stickyTableHeaders(); +``` + +### Tear down +To remove the plugin: + +```js +$('table').stickyTableHeaders('destroy'); +``` + +### Trigger an update manually +```js +$(window).trigger('resize.stickyTableHeaders'); +``` + +### Options +You can initialize the plugin with an options map to tweak the behavior. The following options are supported: + +#### `fixedOffset` +A number or jQuery object specifying how much the sticky header should be offset from the top of the page: + +```js +$('table').stickyTableHeaders({fixedOffset: $('#header')}); +``` + +#### `scrollableArea` +A DOM element or jQuery object. Allows you to overwrite which surrounding element is scrolling. Defaults to `window`. [Check this demo for an example](https://github.com/jmosbech/StickyTableHeaders/tree/master/demo/scrollable-div.html): + +```js +$('table').stickyTableHeaders({scrollableArea: $('.scrollable-area')}); +``` + +#### `cacheHeaderHeight` +Performance tweak: When set to `true` the plugin will only recalculate the height of the header cells when the width of the table changes. + +Default value: `false` + +```js +$('table').stickyTableHeaders({cacheHeaderHeight: true}); +``` + +#### z-index +The plugin uses z-index to make the thead overlay the body. You can override the z-index value by passing in a `zIndex` option: + +```js +$('table').stickyTableHeaders({zIndex: 999}); +``` + +### Reinitialize +As described in [pull request #33](https://github.com/jmosbech/StickyTableHeaders/pull/33) responsive pages might need to reinitialize the plugin when the user resizes his browser. This is can be done by calling the plugin with the new options: + +```js +$('table').stickyTableHeaders({fixedOffset: [new-offset]}); +``` + +### Events +The plugin triggers the following events on the targeted `
` element: + + - `clonedHeader.stickyTableHeaders`: When the header clone is created. + - `enabledStickiness.stickyTableHeaders`: When the sticky header is enabled. + - `disabledStickiness.stickyTableHeaders`: When the sticky header is disabled. + +Confused? +--------- + +If any of this is confusing, please check out the [/demo](https://github.com/jmosbech/StickyTableHeaders/tree/master/demo) folder. There are a couple of examples in there. E.g. you can see how to use it with Twitter Bootstrap. + +Known Issues +------------ +- Internet Explorer: You need to set the padding of the `
`s explicitly in the css in order to make the plugin work +- Internet Explorer: Adding horizontal margin to the table causes the header to be misaligned when scrolling. (Issue #10) +- Using the plugin together with [tablesorter](http://tablesorter.com/docs/) breaks in Internet Explorer 8 + + +Browser Support +--------------- +The plugin has been verified to work in: + +- Chrome 35 +- Firefox 29 +- Internet Explorer 8-11 +- Safari 5 + +NOTE: It does not work in Internet Explorer 7 (but it degrades nicely) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e4ee320 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,11 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "sticky-table-headers": { + "version": "0.1.24", + "resolved": "https://registry.npmjs.org/sticky-table-headers/-/sticky-table-headers-0.1.24.tgz", + "integrity": "sha1-euofB5HKOgKwhgzYhe11ehrL84E=" + } + } +}