Copied starter Meteor App files.
Cut and paste of the BasicMeteorApp.
This commit is contained in:
17
imports/api/Roles.js
Normal file
17
imports/api/Roles.js
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
if(Meteor.isServer) {
|
||||
Meteor.publish('roles', function() {
|
||||
if(Roles.userIsInRole(this.userId, ['manage'])) {
|
||||
return Meteor.roles.find({}, {fields: {name: 1}});
|
||||
}
|
||||
else throw new Meteor.Error(403, "Not authorized to view roles.");
|
||||
});
|
||||
}
|
||||
|
||||
let ROLE_MANAGE = "manage";
|
||||
let ROLE_UPDATE = "update";
|
||||
|
||||
Meteor.UserRoles = {ROLE_MANAGE, ROLE_UPDATE};
|
||||
|
||||
|
||||
export default Meteor.roles;
|
||||
69
imports/api/User.js
Normal file
69
imports/api/User.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import {Random} from 'meteor/random';
|
||||
|
||||
if(Meteor.isServer) {
|
||||
Meteor.publish('users', function() {
|
||||
if(Roles.userIsInRole(this.userId, ['manage'])) {
|
||||
return Meteor.users.find({}, {fields: {username: 1, emails: 1, roles: 1}});
|
||||
}
|
||||
else throw new Meteor.Error(403, "Not authorized to view users.");
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
"insertUser": function(user) {
|
||||
check(user, {
|
||||
username: String,
|
||||
email: String,
|
||||
roles: [String]
|
||||
});
|
||||
|
||||
//Verify the currently logged in user has authority to manage users.
|
||||
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_MANAGE])) {
|
||||
//Verify the user name isn't already used.
|
||||
if(Meteor.collections.Users.findOne({username: user.username}) === undefined) {
|
||||
let pwd = Random.secret(20);
|
||||
let id = Accounts.createUser({password: pwd, username: user.username, email: user.email});
|
||||
|
||||
//Requires the alanning:roles package.
|
||||
Roles.addUsersToRoles(id, user.roles);
|
||||
}
|
||||
else {
|
||||
throw new Meteor.Error(400, "User already exists.");
|
||||
}
|
||||
}
|
||||
else throw new Meteor.Error(403, "Not authorized to add users.");
|
||||
},
|
||||
"updateUser": function(user) {
|
||||
check(user, {
|
||||
_id: String,
|
||||
username: String,
|
||||
emails: [{
|
||||
address: String,
|
||||
verified: Boolean
|
||||
}],
|
||||
roles: [String]
|
||||
});
|
||||
|
||||
//Verify the currently logged in user has authority to manage users.
|
||||
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_MANAGE])) {
|
||||
//Verify the user name isn't already used with a different ID.
|
||||
if(Meteor.collections.Users.findOne({username: user.username, _id: {$ne: user._id}}) == undefined) {
|
||||
//Update the user. Note: I am using direct mongo modification, versus attempting to go through the Accounts and Roles objects. This could cause problems in the future if these packages change their data structures.
|
||||
Meteor.collections.Users.update(user._id, {$set: {username: user.username, emails: user.emails, roles: user.roles}});
|
||||
}
|
||||
else {
|
||||
throw new Meteor.Error(400, "User name already exists.");
|
||||
}
|
||||
}
|
||||
else throw new Meteor.Error(403, "Not authorized to update users.");
|
||||
},
|
||||
"deleteUser": function(id) {
|
||||
check(id, String);
|
||||
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_MANAGE])) {
|
||||
Meteor.collections.Users.remove(id);
|
||||
}
|
||||
else throw new Meteor.Error(403, "Not authorized to remove users.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default Meteor.users;
|
||||
23
imports/api/index.js
Normal file
23
imports/api/index.js
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
import Users from "./User.js";
|
||||
import UserRoles from "./Roles.js";
|
||||
|
||||
//Save the collections in the Meteor.collections property for easy access without name conflicts.
|
||||
Meteor.collections = {Users, UserRoles};
|
||||
|
||||
//If this is the server then setup the default admin user if none exist.
|
||||
if(Meteor.isServer) {
|
||||
//Change this to find admin users, create a default admin user if none exists.
|
||||
if(Users.find({}).count() == 0) {
|
||||
try {
|
||||
console.log("Creating a default admin user: admin/admin");
|
||||
|
||||
let id = Accounts.createUser({password: 'admin', username: 'admin'});
|
||||
//Requires the alanning:roles package.
|
||||
Roles.addUsersToRoles(id, [Meteor.UserRoles.ROLE_MANAGE, Meteor.UserRoles.ROLE_UPDATE]);
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
imports/startup/both/accounts.js
Normal file
83
imports/startup/both/accounts.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import { AccountsTemplates } from 'meteor/useraccounts:core';
|
||||
|
||||
AccountsTemplates.configure({
|
||||
forbidClientAccountCreation: true, //Turn off client side account creation. The app is expected to have a feature that will do this.
|
||||
showForgotPasswordLink: true,
|
||||
defaultTemplate: 'OverrideAtForm',
|
||||
//defaultTemplate: 'AuthorizationPage', //The template for all the forms related to logging in or out.
|
||||
defaultLayout: 'Login', //What page template to place the defaultTemplate in.
|
||||
defaultContentRegion: 'content', //The content region of the page template to place the defaultTemplate in.
|
||||
defaultLayoutRegions: {},
|
||||
// defaultLayout: 'Body',
|
||||
// defaultContentRegion: 'content',
|
||||
// defaultLayoutRegions: {}
|
||||
texts: {
|
||||
title: {
|
||||
signIn: ""
|
||||
},
|
||||
button: {
|
||||
signIn: "Enter"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// This removes the password field but returns it,
|
||||
// so that you can re-add it later, preserving the
|
||||
// desired order of the fields
|
||||
// let pwd = AccountsTemplates.removeField('password');
|
||||
// AccountsTemplates.removeField('email');
|
||||
// AccountsTemplates.addFields([
|
||||
// {
|
||||
// _id: "username",
|
||||
// type: "text",
|
||||
// displayName: "username",
|
||||
// required: true,
|
||||
// minLength: 5,
|
||||
// },
|
||||
// pwd
|
||||
// ]);
|
||||
let pwd = AccountsTemplates.removeField('password');
|
||||
|
||||
AccountsTemplates.removeField('email');
|
||||
AccountsTemplates.addFields([
|
||||
{
|
||||
_id: "username",
|
||||
type: "text",
|
||||
displayName: "username",
|
||||
required: true,
|
||||
minLength: 5,
|
||||
},
|
||||
{
|
||||
_id: 'email',
|
||||
type: 'email',
|
||||
required: true,
|
||||
displayName: "email",
|
||||
re: /.+@(.+){2,}\.(.+){2,}/,
|
||||
errStr: 'Invalid email',
|
||||
},
|
||||
{
|
||||
_id: 'username_and_email',
|
||||
type: 'text',
|
||||
required: true,
|
||||
displayName: "Login",
|
||||
placeholder: "Login / Email"
|
||||
},
|
||||
pwd
|
||||
]);
|
||||
|
||||
//AccountsTemplates.configureRoute('signIn', {
|
||||
// name: 'signin',
|
||||
// path: '/Admin/SignIn'
|
||||
//});
|
||||
//// AccountsTemplates.configureRoute('signUp', {
|
||||
//// name: 'join',
|
||||
//// path: '/join'
|
||||
//// });
|
||||
//AccountsTemplates.configureRoute('forgotPwd', {
|
||||
// name: 'forgotPwd',
|
||||
// path: '/Admin/ForgotPwd'
|
||||
//});
|
||||
//AccountsTemplates.configureRoute('resetPwd', {
|
||||
// name: 'resetPwd',
|
||||
// path: '/Admin/ResetPwd'
|
||||
//});
|
||||
1
imports/startup/both/index.js
Normal file
1
imports/startup/both/index.js
Normal file
@@ -0,0 +1 @@
|
||||
import './accounts.js';
|
||||
1
imports/startup/client/index.js
Normal file
1
imports/startup/client/index.js
Normal file
@@ -0,0 +1 @@
|
||||
import './routes.js';
|
||||
56
imports/startup/client/routes.js
Normal file
56
imports/startup/client/routes.js
Normal file
@@ -0,0 +1,56 @@
|
||||
require('/imports/startup/both/accounts.js'); //Ensure that the user accounts templates are setup first. We have included the AccountTemplates routing in this document to keep all routing in one place.
|
||||
|
||||
//**** GROUPS
|
||||
let pub = FlowRouter.group({
|
||||
});
|
||||
let pri = FlowRouter.group({
|
||||
//TODO: Require SSL
|
||||
triggersEnter: [AccountsTemplates.ensureSignedIn]
|
||||
});
|
||||
|
||||
//**** ADMIN
|
||||
pri.route("/admin", {
|
||||
triggersEnter: [function(context, redirect) {redirect("/Admin/Home");}]
|
||||
});
|
||||
pri.route("/Admin", {
|
||||
triggersEnter: [function(context, redirect) {redirect("/Admin/Home");}]
|
||||
});
|
||||
AccountsTemplates.configureRoute('signIn', {
|
||||
name: 'SignIn',
|
||||
path: '/Admin/SignIn'
|
||||
});
|
||||
AccountsTemplates.configureRoute('resetPwd', {
|
||||
name: 'ResetPwd',
|
||||
path: '/Admin/ResetPwd'
|
||||
});
|
||||
AccountsTemplates.configureRoute('forgotPwd', {
|
||||
name: 'ForgotPwd',
|
||||
path: '/Admin/ForgotPwd'
|
||||
});
|
||||
|
||||
pri.route("/Admin/Home", {
|
||||
name: "AdminHome",
|
||||
action: function(params, queryParams) {
|
||||
require("/imports/ui/AdminHome.js");
|
||||
BlazeLayout.render("Admin", {content: "AdminHome"})
|
||||
}
|
||||
});
|
||||
pri.route("/Admin/UserManagement", {
|
||||
name: "UserManagement",
|
||||
action: function(params, queryParams) {
|
||||
require("/imports/ui/UserManagement.js");
|
||||
BlazeLayout.render("Admin", {content: "UserManagement"})
|
||||
}
|
||||
});
|
||||
|
||||
//*** PUBLIC
|
||||
pub.route('/', {
|
||||
triggersEnter: [function(context, redirect) {redirect("/Home");}]
|
||||
});
|
||||
pub.route("/Home", {
|
||||
name: 'Home',
|
||||
action: function(params, queryParams) {
|
||||
require("/imports/ui/Home.js");
|
||||
BlazeLayout.render("Public", {content: "Home"});
|
||||
}
|
||||
});
|
||||
12
imports/startup/server/email.js
Normal file
12
imports/startup/server/email.js
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
Accounts.emailTemplates.from = "Do Not Reply <administrator@declarativeengineering.com>";
|
||||
Accounts.emailTemplates.siteName = "Petit Teton App";
|
||||
// Accounts.emailTemplates.verifyEmail.subject = function (user) {
|
||||
// return "Welcome to My Site! Please verify your email";
|
||||
// };
|
||||
//
|
||||
// Accounts.emailTemplates.verifyEmail.html = function (user, url) {
|
||||
// return "Hi " + user.profile.firstName + " " + user.profile.lastName + ",\n\n" +
|
||||
// " Please verify your email by simply clicking the link below:\n\n" +
|
||||
// url;
|
||||
// };
|
||||
2
imports/startup/server/index.js
Normal file
2
imports/startup/server/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import "./email.js"
|
||||
import "./../../util/polyfills/date.js"
|
||||
33
imports/startup/server/postStartup/version.js
Normal file
33
imports/startup/server/postStartup/version.js
Normal file
@@ -0,0 +1,33 @@
|
||||
let AppVersion = new Mongo.Collection('AppVersion');
|
||||
let AppVersionSchema = new SimpleSchema({
|
||||
version: {
|
||||
type: Number,
|
||||
optional: false,
|
||||
defaultValue: 0
|
||||
}
|
||||
});
|
||||
AppVersion.attachSchema(AppVersionSchema);
|
||||
|
||||
try {
|
||||
let appVersions = AppVersion.find({}).fetch();
|
||||
let appVersion;
|
||||
|
||||
if(!appVersions || appVersions.length == 0) { //This will happen only when first creating a database.
|
||||
appVersion = {version: 0};
|
||||
appVersion._id = AppVersion.insert(appVersion);
|
||||
}
|
||||
else if(appVersions.length > 1) { //This should never happen. Remove all but the first app version.
|
||||
for(let i = 1; i < appVersions.length; i++) {
|
||||
AppVersion.remove(appVersions[i]._id);
|
||||
}
|
||||
}
|
||||
else {
|
||||
appVersion = appVersions[0];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch(err) {
|
||||
console.log("Caught an error while upgrading the app version: " + err);
|
||||
process.exit(1);
|
||||
}
|
||||
5
imports/ui/AdminHome.html
Normal file
5
imports/ui/AdminHome.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<template name="AdminHome">
|
||||
<div id="adminHome">
|
||||
Hello World
|
||||
</div>
|
||||
</template>
|
||||
2
imports/ui/AdminHome.import.styl
vendored
Normal file
2
imports/ui/AdminHome.import.styl
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
#adminHome
|
||||
margin: 20px 40px
|
||||
2
imports/ui/AdminHome.js
Normal file
2
imports/ui/AdminHome.js
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
import './AdminHome.html';
|
||||
5
imports/ui/Home.html
Normal file
5
imports/ui/Home.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<template name="Home">
|
||||
<div id="homePage">
|
||||
{{#markdown}}This is a test of __markdown__.{{/markdown}}
|
||||
</div>
|
||||
</template>
|
||||
3
imports/ui/Home.import.styl
vendored
Normal file
3
imports/ui/Home.import.styl
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
#homePage
|
||||
display: block
|
||||
min-height: 300px
|
||||
2
imports/ui/Home.js
Normal file
2
imports/ui/Home.js
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
import './Home.html';
|
||||
76
imports/ui/UserManagement.html
Normal file
76
imports/ui/UserManagement.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<template name="UserManagement">
|
||||
<div id="userManagement">
|
||||
{{#if Template.subscriptionsReady}}
|
||||
<div class="tableControls">
|
||||
<div class="contentControls">
|
||||
<a class="loadMoreLink {{#if disableLoadMore}}disabled{{/if}}" href="javascript:">Load More...</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separatedTableHeader">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="username">Username {{>UserSearch columnName='username'}}</th>
|
||||
<th class="email">Email {{>UserSearch columnName='email' collectionQueryColumnName='name' collection='Items' collectionResultColumnName='_id'}}</th>
|
||||
<th class="roles">Roles</th>
|
||||
<th class="actions">Actions <span class="newUserButton btn btn-success"><i class="fa fa-plus-circle" aria-hidden="true"></i><i class="fa fa-times-circle" aria-hidden="true"></i></span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<div class="listRow">
|
||||
<div class="listCell">
|
||||
<div class="tableContainer mCustomScrollbar" data-mcs-theme="dark">
|
||||
<table class="dataTable table table-striped table-hover">
|
||||
<tbody>
|
||||
{{#if displayNewUser}}
|
||||
<tr>{{> UserEditor isNew=true}}</tr>
|
||||
{{/if}}
|
||||
{{#each users}}
|
||||
{{> User}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="User">
|
||||
<tr>
|
||||
{{#if editing}}
|
||||
{{> UserEditor}}
|
||||
{{else}}
|
||||
<td class="username tdLarge noselect nonclickable">{{username}}</td>
|
||||
<td class="email tdLarge noselect nonclickable">{{email}}</td>
|
||||
<td class="roles tdLarge noselect nonclickable">{{roles}}</td>
|
||||
<td class="actions center tdLarge"><i class="userRemove fa fa-times-circle fa-lg noselect clickable" aria-hidden="true"></i> / <i class="userEdit fa fa-pencil-square-o fa-lg noselect clickable" aria-hidden="true"></i></td>
|
||||
{{/if}}
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<template name="UserEditor">
|
||||
<td colspan="3" class="userEditor measureEditorTd">
|
||||
<div>
|
||||
<div class="username editorDiv"><label>User Name:</label><input name="username" class="form-control" type="text" value="{{username}}" autocomplete="off" required></div>
|
||||
<div class="email editorDiv"><label>User Email:</label><input name="email" class="form-control" type="text" value="{{email}}" autocomplete="off" required></div>
|
||||
<div class="rolesContainer editorDiv"><label>Roles:</label>
|
||||
<div class="roles center" style="font-size: 1.2em">
|
||||
{{#each allRoles}}
|
||||
<span class="role {{getRoleState this}} noselect">{{name}}</span>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="actions center measureEditorTd"><i class="editorApply fa fa-check-square-o fa-lg noselect clickable" aria-hidden="true"></i> / <i class="editorCancel fa fa-times-circle fa-lg noselect clickable" aria-hidden="true"></i></td>
|
||||
</template>
|
||||
|
||||
<template name="UserSearch">
|
||||
<div class="">
|
||||
<input type="text" class="searchInput" placeholder="Filter..." value="{{searchValue}}" style="width: 90%"/>
|
||||
</div>
|
||||
</template>
|
||||
145
imports/ui/UserManagement.import.styl
vendored
Normal file
145
imports/ui/UserManagement.import.styl
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
#userManagement
|
||||
display: table
|
||||
content-box: border-box
|
||||
padding: 10px 20px
|
||||
height: 100%
|
||||
width: 100%
|
||||
text-align: left
|
||||
|
||||
.tableControls
|
||||
display: table
|
||||
width: 100%
|
||||
text-align: right
|
||||
margin-right: 20px
|
||||
.contentControls
|
||||
vertical-align: bottom
|
||||
display: table-cell
|
||||
text-align: right
|
||||
min-width: 100px
|
||||
a
|
||||
font-size: 12px
|
||||
font-family: "Arial", san-serif
|
||||
font-weight: 800
|
||||
color: #2d1b8c
|
||||
text-decoration: none
|
||||
a:hover
|
||||
text-decoration: underline
|
||||
a.disabled
|
||||
visibility: hidden
|
||||
.editor
|
||||
height: 100%
|
||||
overflow-y: auto
|
||||
|
||||
.insert
|
||||
flex: none
|
||||
width: 100%
|
||||
|
||||
.col-md-6
|
||||
padding: 10px 30px 0 30px
|
||||
background: #EFEFEF
|
||||
border-radius: 1em
|
||||
|
||||
.formGroupHeading
|
||||
font-size: 1.6em
|
||||
font-family: "Arial Black", "Arial Bold", Gadget, sans-serif
|
||||
font-style: normal
|
||||
font-variant: normal
|
||||
font-weight: 500
|
||||
.table
|
||||
table-layout: fixed
|
||||
min-width: 100%
|
||||
thead, tbody
|
||||
> tr
|
||||
> .username
|
||||
width: 50%
|
||||
min-width: 100px
|
||||
> .email
|
||||
width: 50%
|
||||
min-width: 100px
|
||||
> .roles
|
||||
width: 260px
|
||||
min-width: 260px
|
||||
> .actions
|
||||
width: 80px
|
||||
min-width: 80px
|
||||
.separatedTableHeader
|
||||
.actions
|
||||
text-align: center
|
||||
.newUserButton
|
||||
margin-top: 4px
|
||||
padding: 0 12px
|
||||
.fa-plus-circle
|
||||
display: inline-block
|
||||
.fa-times-circle
|
||||
display: none
|
||||
.newUserButton.active
|
||||
background-color: #fb557b
|
||||
color: black
|
||||
.fa-times-circle
|
||||
display: inline-block
|
||||
.fa-plus-circle
|
||||
display: none
|
||||
.listRow
|
||||
display: table-row
|
||||
.listCell
|
||||
display: table-cell
|
||||
position: relative
|
||||
height: 100%
|
||||
width: 100%
|
||||
.tableContainer
|
||||
position: absolute
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
width: auto
|
||||
height: auto
|
||||
border: 0
|
||||
font-size: 12.5px
|
||||
overflow-y: auto
|
||||
table
|
||||
table-layout: fixed
|
||||
width: 100%
|
||||
thead
|
||||
display: none
|
||||
visibility: hidden
|
||||
.userRemove
|
||||
color: red
|
||||
.userEdit
|
||||
color: darkblue
|
||||
.editorApply
|
||||
color: green
|
||||
.editorCancel
|
||||
color: red
|
||||
.userEditor > div
|
||||
display: table
|
||||
> div
|
||||
display: table-cell
|
||||
padding: 10px
|
||||
.roles
|
||||
.role
|
||||
vertical-align: middle
|
||||
td.roles
|
||||
.role
|
||||
padding: 4px 4px
|
||||
border: 1px solid #555
|
||||
border-radius: .25em
|
||||
background: white
|
||||
color: #999
|
||||
cursor: pointer
|
||||
.selected
|
||||
color: black
|
||||
|
||||
div.roles
|
||||
padding: 4px 0
|
||||
.role
|
||||
padding: 4px 4px
|
||||
border: 1px solid #555
|
||||
border-radius: .25em
|
||||
background: white
|
||||
color: #999
|
||||
cursor: pointer
|
||||
.selected
|
||||
color: black
|
||||
.center
|
||||
vertical-align: middle !important
|
||||
223
imports/ui/UserManagement.js
Normal file
223
imports/ui/UserManagement.js
Normal file
@@ -0,0 +1,223 @@
|
||||
|
||||
import './UserManagement.html';
|
||||
import '/imports/util/selectize/selectize.js'
|
||||
|
||||
let QUERY_LIMIT = 100;
|
||||
let QUERY_LIMIT_INCREMENT = 100;
|
||||
let PREFIX = "UserManagement";
|
||||
|
||||
Tracker.autorun(function() {
|
||||
Meteor.subscribe("users", Session.get(PREFIX + 'searchQuery'));
|
||||
Meteor.subscribe("roles");
|
||||
});
|
||||
|
||||
Template.UserManagement.onCreated(function() {
|
||||
Session.set(PREFIX + "displayNewUser", false);
|
||||
Session.set(PREFIX + "queryLimit", QUERY_LIMIT);
|
||||
});
|
||||
Template.UserManagement.onRendered(function() {
|
||||
$(".tableContainer").mCustomScrollbar({
|
||||
scrollButtons: {enable:true},
|
||||
theme: "light-thick",
|
||||
scrollbarPosition: "outside",
|
||||
scrollEasing: "linear"
|
||||
});
|
||||
});
|
||||
Template.UserManagement.helpers({
|
||||
displayNewUser: function() {
|
||||
return Session.get(PREFIX + "displayNewUser");
|
||||
},
|
||||
users: function() {
|
||||
let skipCount = Session.get(PREFIX + 'skipCount') || 0;
|
||||
let query = Session.get(PREFIX + 'searchQuery');
|
||||
let dbQuery = [];
|
||||
|
||||
if(query) {
|
||||
_.each(_.keys(query), function(key) {
|
||||
if(_.isFunction(query[key])) dbQuery.push({[key]: query[key]}); //dbQuery[key] = query[key]();
|
||||
else if(_.isObject(query[key])) dbQuery.push({[key]: query[key]}); //dbQuery[key] = query[key]; //Will look something like: {$in: [xxx,xxx,xxx]}
|
||||
else if(_.isNumber(query[key])) dbQuery.push({[key]: query[key]}); //dbQuery[key] = query[key];
|
||||
else {
|
||||
//dbQuery[key] = {$regex: query[key], $options: 'i'};
|
||||
let searchValue = query[key];
|
||||
let searches = searchValue && searchValue.length > 0 ? searchValue.split(/\s+/) : undefined;
|
||||
|
||||
for(let search of searches) {
|
||||
dbQuery.push({[key]: {$regex: '\\b' + search, $options: 'i'}});
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if(!Session.get(PREFIX + "showHidden")) {
|
||||
//Ignore any hidden elements by showing those not hidden, or those without the hidden field.
|
||||
dbQuery.push({$or: [{hidden: false}, {hidden: {$exists:false}}]});
|
||||
}
|
||||
|
||||
dbQuery = dbQuery.length > 0 ? {$and: dbQuery} : {};
|
||||
Session.set(PREFIX + 'userCount', Meteor.collections.Users.find(dbQuery).count()); //Always get a full count.
|
||||
return Meteor.collections.Users.find(dbQuery, {limit: Session.get(PREFIX + "queryLimit"), skip: skipCount, sort: {username: 1}});
|
||||
},
|
||||
disableLoadMore: function() {
|
||||
return Session.get(PREFIX + 'userCount') - (Session.get(PREFIX + 'skipCount') || 0) - Session.get(PREFIX + "queryLimit") <= 0;
|
||||
}
|
||||
});
|
||||
Template.UserManagement.events({
|
||||
'click .loadMoreLink': function(event, template) {
|
||||
event.preventDefault();
|
||||
Session.set(PREFIX + 'queryLimit', Session.get(PREFIX + "queryLimit") + QUERY_LIMIT_INCREMENT);
|
||||
},
|
||||
'click .newUserButton': function(event, template) {
|
||||
if(template.$('.newUserButton').hasClass('active')) {
|
||||
Session.set(PREFIX + 'displayNewUser', false);
|
||||
}
|
||||
else {
|
||||
Session.set(PREFIX + 'displayNewUser', true);
|
||||
Session.set(PREFIX + "editedUser", undefined); //Clear the edited user so that only one editor is open at a time.
|
||||
}
|
||||
template.$('.newUserButton').toggleClass('active');
|
||||
}
|
||||
});
|
||||
|
||||
Template.User.onCreated(function() {
|
||||
this.edited = new ReactiveVar();
|
||||
});
|
||||
Template.User.events({
|
||||
"click .userEdit": function(event, template) {
|
||||
//template.edited.set(this);
|
||||
Session.set(PREFIX + "editedUser", this._id);
|
||||
Session.set(PREFIX + 'displayNewUser', false); //Ensure the new measure editor is closed.
|
||||
template.parentTemplate().$('.newUserButton').removeClass('active');
|
||||
},
|
||||
"click .userRemove": function(event, template) {
|
||||
let _this = this;
|
||||
bootbox.confirm({
|
||||
message: "Delete the user?",
|
||||
buttons: {confirm: {label: "Yes", className: 'btn-success'}, cancel: {label: "No", className: "btn-danger"}},
|
||||
callback: function(result) {
|
||||
if(result) {
|
||||
Meteor.call('deleteUser', _this._id, function(error, result) {
|
||||
if(error) {
|
||||
sAlert.error(error);
|
||||
}
|
||||
else {
|
||||
sAlert.success("User removed.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
Template.User.helpers({
|
||||
email: function() {
|
||||
return this.emails && this.emails.length > 0 ? this.emails[0].address : "";
|
||||
},
|
||||
editing: function() {
|
||||
let editedUser = Session.get(PREFIX + "editedUser");
|
||||
|
||||
return editedUser == this._id;
|
||||
}
|
||||
});
|
||||
|
||||
Template.UserEditor.helpers({
|
||||
email: function() {
|
||||
return this.emails && this.emails.length > 0 ? this.emails[0].address : "";
|
||||
},
|
||||
allRoles: function() {
|
||||
return Meteor.collections.UserRoles.find();
|
||||
},
|
||||
getRoleState: function(role) {
|
||||
let user = Template.parentData(1);
|
||||
|
||||
return !user.isNew && user.roles.includes(role.name) ? "selected" : "";
|
||||
}
|
||||
});
|
||||
Template.UserEditor.events({
|
||||
"click .editorCancel": function(event, template) {
|
||||
Session.set(PREFIX + "editedUser", undefined);
|
||||
Session.set(PREFIX + 'displayNewUser', false);
|
||||
template.parentTemplate().$('.newUserButton').removeClass('active');
|
||||
},
|
||||
"click .editorApply": function(event, template) {
|
||||
let user = {};
|
||||
let roles = [];
|
||||
|
||||
user.username = template.$('input[name="username"]').val();
|
||||
user.email = template.$('input[name="email"]').val();
|
||||
|
||||
let roleSpans = template.$('.role.selected');
|
||||
for(let i = 0; i < roleSpans.length; i++) {
|
||||
roles.push($(roleSpans[i]).text());
|
||||
}
|
||||
|
||||
user.roles = roles;
|
||||
|
||||
if(Session.get(PREFIX + 'displayNewUser')) {
|
||||
Meteor.call('insertUser', user, function(error, result) {
|
||||
if(error) {
|
||||
sAlert.error(error);
|
||||
}
|
||||
else {
|
||||
sAlert.success("User created.");
|
||||
Session.set(PREFIX + 'displayNewUser', false);
|
||||
template.parentTemplate().$('.newUserButton').removeClass('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
user._id = this._id;
|
||||
Meteor.call("updateUser", user, function(error, result) {
|
||||
if(error) sAlert.error(error);
|
||||
else {
|
||||
sAlert.success("User updated.");
|
||||
Session.set(PREFIX + "editedUser", undefined);
|
||||
template.parentTemplate().$('.newUserButton').removeClass('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
"click .role": function(event, template) {
|
||||
$(event.target).toggleClass("selected");
|
||||
}
|
||||
});
|
||||
|
||||
Template.UserSearch.events({
|
||||
"keyup .searchInput": _.throttle(function(event, template) {
|
||||
let searchQuery = Session.get(PREFIX + 'searchQuery') || {};
|
||||
let searchFields = Session.get(PREFIX + 'searchFields') || {};
|
||||
let searchValue = template.$('.searchInput').val();
|
||||
|
||||
if(searchValue) {
|
||||
if(this.number) searchValue = parseFloat(searchValue);
|
||||
|
||||
if(this.collection) {
|
||||
let ids = Meteor.collections[this.collection].find({[this.collectionQueryColumnName]: {$regex: searchValue, $options: 'i'}}, {fields: {[this.collectionResultColumnName]: 1}}).fetch();
|
||||
|
||||
//Convert the ids to an array of ids instead of an array of objects containing an id.
|
||||
for(let i = 0; i < ids.length; i++) {ids[i] = ids[i]._id;}
|
||||
searchQuery[this.columnName] = {$in: ids};
|
||||
searchFields[this.columnName] = searchValue;
|
||||
}
|
||||
else {
|
||||
searchFields[this.columnName] = searchQuery[this.columnName] = searchValue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
//Remove columns from the search query whose values are empty so we don't bother the database with them.
|
||||
delete searchQuery[this.columnName];
|
||||
delete searchFields[this.columnName];
|
||||
}
|
||||
|
||||
Session.set(PREFIX + 'searchQuery', searchQuery);
|
||||
Session.set(PREFIX + 'skipCount', 0); //Reset the paging of the results.
|
||||
}, 500)
|
||||
});
|
||||
Template.UserSearch.helpers({
|
||||
searchValue: function() {
|
||||
let searchFields = Session.get(PREFIX + 'searchFields');
|
||||
|
||||
return (searchFields && searchFields[this.columnName]) ? searchFields[this.columnName] : '';
|
||||
}
|
||||
});
|
||||
58
imports/ui/accounts/accounts.html
Normal file
58
imports/ui/accounts/accounts.html
Normal file
@@ -0,0 +1,58 @@
|
||||
<template name="OverrideAtForm">
|
||||
{{#unless hide}}
|
||||
<div class="at-form">
|
||||
<!--{{#if showTitle}}-->
|
||||
<!--{{> atTitle}}-->
|
||||
<!--{{/if}}-->
|
||||
{{#if showOauthServices}}
|
||||
{{> atOauth}}
|
||||
{{/if}}
|
||||
{{#if showServicesSeparator}}
|
||||
{{> atSep}}
|
||||
{{/if}}
|
||||
{{#if showError}}
|
||||
{{> atError}}
|
||||
{{/if}}
|
||||
{{#if showResult}}
|
||||
{{> atResult}}
|
||||
{{/if}}
|
||||
{{#if showMessage}}
|
||||
{{> atMessage}}
|
||||
{{/if}}
|
||||
{{#if showPwdForm}}
|
||||
{{> OverrideAtPwdForm}}
|
||||
{{/if}}
|
||||
{{#if showTermsLink}}
|
||||
{{> atTermsLink}}
|
||||
{{/if}}
|
||||
{{#if showSignInLink}}
|
||||
{{> atSigninLink}}
|
||||
{{/if}}
|
||||
{{#if showSignUpLink}}
|
||||
{{> atSignupLink}}
|
||||
{{/if}}
|
||||
{{#if showResendVerificationEmailLink}}
|
||||
{{> atResendVerificationEmailLink}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
</template>
|
||||
|
||||
<template name="OverrideAtPwdForm">
|
||||
<div class="at-pwd-form">
|
||||
<form role="form" id="at-pwd-form" novalidate action="#" method="POST">
|
||||
<fieldset {{disabled}}>
|
||||
{{#each fields}}
|
||||
{{> atInput}}
|
||||
{{/each}}
|
||||
{{#if showReCaptcha}}
|
||||
{{> atReCaptcha}}
|
||||
{{/if}}
|
||||
{{> atPwdFormBtn}}
|
||||
{{#if showForgotPasswordLink}}
|
||||
{{> atPwdLink}}
|
||||
{{/if}}
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
18
imports/ui/accounts/accounts.js
Normal file
18
imports/ui/accounts/accounts.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Template } from 'meteor/templating';
|
||||
|
||||
import './accounts.html';
|
||||
|
||||
// Simply 'inherits' helpers from AccountsTemplates
|
||||
Template.OverrideAtForm.helpers(AccountsTemplates.atFormHelpers);
|
||||
|
||||
// Simply 'inherits' helpers and events from AccountsTemplates
|
||||
Template.OverrideAtPwdForm.helpers(AccountsTemplates.atPwdFormHelpers);
|
||||
Template.OverrideAtPwdForm.events(AccountsTemplates.atPwdFormEvents);
|
||||
|
||||
// We identified the templates that need to be overridden by looking at the available templates
|
||||
// here: https://github.com/meteor-useraccounts/unstyled/tree/master/lib
|
||||
// Template['override-atPwdFormBtn'].replaces('atPwdFormBtn');
|
||||
// Template['override-atPwdForm'].replaces('atPwdForm');
|
||||
// Template['override-atTextInput'].replaces('atTextInput');
|
||||
// Template['override-atTitle'].replaces('atTitle');
|
||||
// Template['override-atError'].replaces('atError');
|
||||
8
imports/ui/helpers.js
Normal file
8
imports/ui/helpers.js
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
// General use helpers - available to all views.
|
||||
|
||||
UI.registerHelper('currentUserName', function() {
|
||||
if(Meteor.user()){
|
||||
return Meteor.user().emails[0].address;
|
||||
}
|
||||
});
|
||||
32
imports/ui/layouts/Admin.html
Normal file
32
imports/ui/layouts/Admin.html
Normal file
@@ -0,0 +1,32 @@
|
||||
<template name="Admin">
|
||||
{{> sAlert}}
|
||||
<div id="mainAdmin">
|
||||
<nav class="leftSidebarContainer generalSidebar">
|
||||
<a href="javascript:" class="fa fa-bars leftSidebarMenuButton generalMenuButton" aria-hidden="true"></a>
|
||||
<div class="leftSidebar">
|
||||
<div class="logoArea">
|
||||
<i class="fa fa-sign-out fa-2x signOut" aria-hidden="true"></i>
|
||||
<div class="logo"><img src="/images/Logo_v1.png" height="60px"/></div>
|
||||
</div>
|
||||
<div class="menuArea generalMenu">
|
||||
<ul>
|
||||
<li class="{{isActiveRoute 'UserManagement'}}">
|
||||
<a href="{{pathFor 'UserManagement'}}">
|
||||
Users
|
||||
</a>
|
||||
</li>
|
||||
<!-- Below is a second menu with a tag attached. -->
|
||||
<!--<li class="{{isActiveRoute 'Misc'}}">-->
|
||||
<!--<a href="{{pathFor 'Misc'}}">-->
|
||||
<!--Misc <!– <span class="tag">sample tag</span>–>-->
|
||||
<!--</a>-->
|
||||
<!--</li>-->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="contentBody verticalStack">
|
||||
{{> Template.dynamic template=content}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
189
imports/ui/layouts/Admin.import.styl
vendored
Normal file
189
imports/ui/layouts/Admin.import.styl
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
|
||||
#mainAdmin
|
||||
margin: 0
|
||||
padding: 0
|
||||
border: 0
|
||||
height: 100%
|
||||
width: 100%
|
||||
|
||||
nav.leftSidebarContainer
|
||||
z-index:999
|
||||
position: fixed
|
||||
top: 0
|
||||
width: 220px
|
||||
padding: 0
|
||||
height: 100%
|
||||
border: 0
|
||||
vertical-align: top
|
||||
text-align: left
|
||||
background-color: #90b272 //Old browsers
|
||||
background: -moz-linear-gradient(-180deg, #90b272 0%, #4d7727 100%) //FF3.6-15
|
||||
background: -webkit-linear-gradient(-180deg, #90b272 0%,#4d7727 100%) //Chrome10-25,Safari5.1-6
|
||||
background: linear-gradient(180deg, #90b272 0%,#4d7727 100%) //W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+
|
||||
font-size: 14px
|
||||
font-weight: 700
|
||||
overflow: visible
|
||||
margin: 0 0 0 -220px
|
||||
-webkit-transition: .5s ease-in
|
||||
-moz-transition: .5s ease-in
|
||||
-o-transition: .5s ease-in
|
||||
-ms-transition: .5s ease-in
|
||||
transition: .5s ease-in
|
||||
.leftSidebarMenuButton
|
||||
position: absolute
|
||||
right: -30px
|
||||
-webkit-transition: .5s ease-in
|
||||
-moz-transition: .5s ease-in
|
||||
-o-transition: .5s ease-in
|
||||
-ms-transition: .5s ease-in
|
||||
transition: .5s ease-in
|
||||
-webkit-border-top-right-radius: 5px
|
||||
-webkit-border-bottom-right-radius: 5px
|
||||
-moz-border-radius-topright: 5px
|
||||
-moz-border-radius-bottomright: 5px
|
||||
border-top-right-radius: 5px
|
||||
border-bottom-right-radius: 5px
|
||||
color: black
|
||||
font-size: 20px
|
||||
line-height: 20px
|
||||
font-weight: 900
|
||||
text-align: center
|
||||
text-decoration: none
|
||||
width: 30px
|
||||
height: 30px
|
||||
padding: 5px 0
|
||||
background-color: #90b272
|
||||
display: block
|
||||
border-top: 1px solid #494
|
||||
border-right: 1px solid #494
|
||||
border-bottom: 1px solid #494
|
||||
.leftSidebarMenuButton:hover
|
||||
color: rgba(150,0,0,.5)
|
||||
nav.generalSidebar
|
||||
.leftSidebarMenuButton
|
||||
top: 10px
|
||||
nav.menuHide .leftSidebarMenuButton
|
||||
right: 60px
|
||||
nav.menuShow
|
||||
margin: 0
|
||||
nav.menuShow .leftSidebarMenuButton
|
||||
right: -15px
|
||||
-webkit-transform: rotate(45deg) !important
|
||||
-moz-transform: rotate(45deg) !important
|
||||
-o-transform: rotate(45deg) !important
|
||||
-ms-transform: rotate(45deg) !important
|
||||
transform: rotate(45deg) !important
|
||||
-moz-border-radius-bottomright: 0
|
||||
//border-top-right-radius: 0
|
||||
border-bottom-right-radius: 0
|
||||
border-bottom: 0
|
||||
.leftSidebar
|
||||
height: 100%
|
||||
//position: absolute
|
||||
border: 0
|
||||
vertical-align: top
|
||||
padding: 0
|
||||
text-align: left
|
||||
//top: 0px
|
||||
//left: 0px
|
||||
//bottom: 0px
|
||||
width: 220px
|
||||
//Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#627d4d+0,1f3b08+100;Olive+3D
|
||||
background-color: #90b272 //Old browsers
|
||||
background: -moz-linear-gradient(-180deg, #90b272 0%, #4d7727 100%) //FF3.6-15
|
||||
background: -webkit-linear-gradient(-180deg, #90b272 0%,#4d7727 100%) //Chrome10-25,Safari5.1-6
|
||||
background: linear-gradient(180deg, #90b272 0%,#4d7727 100%) //W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+
|
||||
font-size: 14px
|
||||
font-weight: 700
|
||||
overflow: hidden
|
||||
|
||||
.logoArea
|
||||
width: 100%
|
||||
min-height: 50px
|
||||
.signOut
|
||||
position: absolute
|
||||
left: 10px
|
||||
top: 10px
|
||||
color: white
|
||||
cursor: pointer
|
||||
.signOut:hover
|
||||
color: #BBB
|
||||
.signOut:active
|
||||
color: black
|
||||
.logo
|
||||
text-align: center
|
||||
margin-top: 20px
|
||||
img:hover
|
||||
//-webkit-animation: neon6_drop 1.5s ease-in-out infinite alternate;
|
||||
//-moz-animation: neon6_drop 1.5s ease-in-out infinite alternate;
|
||||
animation: neon6_drop 1.5s ease-in-out infinite alternate;
|
||||
.menuArea
|
||||
width: 100%
|
||||
ul
|
||||
padding: 20px 0 0 0
|
||||
margin: 0
|
||||
list-style: none
|
||||
li:first-child
|
||||
border-top: 1px solid #e4e5e7
|
||||
li
|
||||
border-bottom: 1px solid #e4e5e7
|
||||
color: #96a2ae
|
||||
text-transform: uppercase
|
||||
display: block
|
||||
a
|
||||
color: black
|
||||
padding: 10px 20px
|
||||
cursor: pointer
|
||||
text-decoration: none
|
||||
display: block
|
||||
.tag
|
||||
padding: .3em .6em
|
||||
margin-top: -.2em
|
||||
font-size: .8em
|
||||
color: #ddd
|
||||
white-space: nowrap
|
||||
vertical-align: baseline
|
||||
border-radius: .5em
|
||||
border: 1px solid #000000
|
||||
float: right
|
||||
.subMenu
|
||||
background-color: #999
|
||||
padding: .3em .6em
|
||||
margin-top: -.2em
|
||||
font-size: .8em
|
||||
color: #fff
|
||||
white-space: nowrap
|
||||
vertical-align: baseline
|
||||
border-radius: .5em
|
||||
border: 1px solid #000000
|
||||
float: right
|
||||
.subMenu.active
|
||||
background-color: #333
|
||||
li:hover
|
||||
// Note: neon6 is defined in effects.import.styl
|
||||
background-color: #666
|
||||
-webkit-animation: neon6 1.5s ease-in-out infinite alternate
|
||||
-moz-animation: neon6 1.5s ease-in-out infinite alternate
|
||||
animation: neon6 1.5s ease-in-out infinite alternate
|
||||
.subMenu
|
||||
// Note: neon6 is defined in effects.import.styl
|
||||
background-color: #999
|
||||
-webkit-animation: neon7 1.5s ease-in-out infinite alternate
|
||||
-moz-animation: neon7 1.5s ease-in-out infinite alternate
|
||||
animation: neon7 1.5s ease-in-out infinite alternate
|
||||
li.active
|
||||
background-color: #333
|
||||
> a
|
||||
color: #96a2ae
|
||||
li.active:hover
|
||||
background-color: #333
|
||||
> a
|
||||
color: white
|
||||
|
||||
.contentBody
|
||||
flex: 1 1 1px
|
||||
padding: 10px 20px
|
||||
-webkit-box-shadow: inset 4px 2px 10px -3px rgba(168,165,168,1)
|
||||
-moz-box-shadow: inset 4px 2px 10px -3px rgba(168,165,168,1)
|
||||
box-shadow: inset 8px 0px 10px -3px rgba(168,165,168,1)
|
||||
overflow: hidden
|
||||
54
imports/ui/layouts/Admin.js
Normal file
54
imports/ui/layouts/Admin.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Template } from 'meteor/templating';
|
||||
import './Admin.html';
|
||||
|
||||
Template.Admin.toggleMenu = function($sidebar) {
|
||||
let $sidebars = $('nav.leftSidebarContainer');
|
||||
|
||||
for(let i = 0; i < $sidebars.length; i++) {
|
||||
if($sidebars[i] == $sidebar[0]) {
|
||||
$sidebar.toggleClass('menuShow');
|
||||
}
|
||||
else {
|
||||
$($sidebars[i]).toggleClass('menuHide');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Template.Admin.events({
|
||||
"click .signOut": function(event, template) {
|
||||
AccountsTemplates.logout();
|
||||
},
|
||||
// General Menu
|
||||
"click .generalSidebar .leftSidebarMenuButton": function(event, template) {
|
||||
event.preventDefault();
|
||||
Template.Admin.toggleMenu($('nav.generalSidebar'));
|
||||
},
|
||||
"click .generalSidebar .leftSidebar a": function(event, template) {
|
||||
Template.Admin.toggleMenu($('nav.generalSidebar'));
|
||||
},
|
||||
"click .generalSidebar .leftSidebar a .subMenu": function(event, template) {
|
||||
Template.Admin.toggleMenu($('nav.generalSidebar'));
|
||||
},
|
||||
// Graphs Menu
|
||||
"click .graphsSidebar .leftSidebarMenuButton": function(event, template) {
|
||||
event.preventDefault();
|
||||
Template.Admin.toggleMenu($('nav.graphsSidebar'));
|
||||
},
|
||||
"click .graphsSidebar .leftSidebar a": function(event, template) {
|
||||
Template.Admin.toggleMenu($('nav.graphsSidebar'));
|
||||
},
|
||||
"click .graphsSidebar .leftSidebar a .subMenu": function(event, template) {
|
||||
Template.Admin.toggleMenu($('nav.graphsSidebar'));
|
||||
},
|
||||
// Settings Menu
|
||||
"click .settingsSidebar .leftSidebarMenuButton": function(event, template) {
|
||||
event.preventDefault();
|
||||
Template.Admin.toggleMenu($('nav.settingsSidebar'));
|
||||
},
|
||||
"click .settingsSidebar .leftSidebar a": function(event, template) {
|
||||
Template.Admin.toggleMenu($('nav.settingsSidebar'));
|
||||
},
|
||||
"click .settingsSidebar .leftSidebar a .subMenu": function(event, template) {
|
||||
Template.Admin.toggleMenu($('nav.settingsSidebar'));
|
||||
}
|
||||
});
|
||||
9
imports/ui/layouts/Login.html
Normal file
9
imports/ui/layouts/Login.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<template name="Login">
|
||||
<div id="login" class="content">
|
||||
<div class="spacer"> </div>
|
||||
<div class="contentBox">
|
||||
<img src="/images/Logo_v1.png"/>
|
||||
<div class="form">{{> Template.dynamic template=content}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
63
imports/ui/layouts/Login.import.styl
vendored
Normal file
63
imports/ui/layouts/Login.import.styl
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
#login.content
|
||||
background: white
|
||||
height: 100%
|
||||
.spacer
|
||||
height: 10%
|
||||
.contentBox
|
||||
margin: 0 auto
|
||||
max-width: 600px
|
||||
background-color: #88d15a
|
||||
-webkit-box-shadow: 10px 10px 5px 0px rgba(0,0,0,0.5);
|
||||
-moz-box-shadow: 10px 10px 5px 0px rgba(0,0,0,0.5);
|
||||
box-shadow: 10px 10px 5px 0px rgba(0,0,0,0.5);
|
||||
padding: 40px 10px
|
||||
text-align: center
|
||||
img
|
||||
height: 120px
|
||||
vertical-align: top
|
||||
.form
|
||||
display: inline-block
|
||||
margin-left: 20px
|
||||
input
|
||||
padding: 8px
|
||||
width: 300px
|
||||
margin-bottom: 10px
|
||||
label
|
||||
display: none
|
||||
fieldset
|
||||
border: none
|
||||
.at-btn
|
||||
margin-bottom: 6px
|
||||
text-align: center
|
||||
width: 300px
|
||||
background: #34d955;
|
||||
background-image: -webkit-linear-gradient(top, #5d942b, #4b7d26)
|
||||
background-image: -moz-linear-gradient(top, #5d942b, #4b7d26)
|
||||
background-image: -ms-linear-gradient(top, #5d942b, #4b7d26)
|
||||
background-image: -o-linear-gradient(top, #5d942b, #4b7d26)
|
||||
background-image: linear-gradient(to bottom, #5d942b, #4b7d26)
|
||||
font-family: "Arial Black", Arial
|
||||
color: #ffffff
|
||||
font-size: 14px
|
||||
line-height: 16px
|
||||
padding: 10px 20px 10px 20px
|
||||
text-decoration: none
|
||||
text-transform: uppercase
|
||||
border: none
|
||||
.at-btn:hover
|
||||
background: #29b54f
|
||||
background-image: -webkit-linear-gradient(top, #29b54f, #186b31)
|
||||
background-image: -moz-linear-gradient(top, #29b54f, #186b31)
|
||||
background-image: -ms-linear-gradient(top, #29b54f, #186b31)
|
||||
background-image: -o-linear-gradient(top, #29b54f, #186b31)
|
||||
background-image: linear-gradient(to bottom, #29b54f, #186b31)
|
||||
text-decoration: none
|
||||
cursor: pointer
|
||||
.at-link
|
||||
color: #1555b4
|
||||
font: Arial
|
||||
font-size: 12px
|
||||
font-weight: 800
|
||||
text-decoration: none
|
||||
.at-link:hover
|
||||
text-decoration: underline
|
||||
1
imports/ui/layouts/Login.js
Normal file
1
imports/ui/layouts/Login.js
Normal file
@@ -0,0 +1 @@
|
||||
import './Login.html';
|
||||
29
imports/ui/layouts/Public.html
Normal file
29
imports/ui/layouts/Public.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<template name="Public">
|
||||
{{> sAlert}}
|
||||
<div id="publicBody">
|
||||
<div id="page">
|
||||
<div id="menu"><!-- Note: Comment out spacing between the elements since the browser will interpret the spaces as characters to be displayed.
|
||||
--><a href="/Home">Home</a><!--
|
||||
--></div>
|
||||
<div id="links">
|
||||
<!-- Note: Comment out spacing between the elements since the browser will interpret the spaces as characters to be displayed. -->
|
||||
<a id="linkFacebook" href="" target='_blank' rel="nofollow"></a><!--
|
||||
--><a id="linkGoogle" href="" target='_blank' rel="nofollow"></a><!--
|
||||
--><a id="linkTwitter" href="" target='_blank' rel="nofollow"></a>
|
||||
</div>
|
||||
<div id="menuBackground"></div> <!-- A spacer between the menu and the header image. -->
|
||||
<div id="head">
|
||||
<!-- Logo click takes the user back to home -->
|
||||
<div id="logo" onclick="window.location='/Home'"></div>
|
||||
</div>
|
||||
<div id="content">
|
||||
{{> Template.dynamic template=content}}
|
||||
</div>
|
||||
</div>
|
||||
<!-- footer lines -->
|
||||
<div id="footer">© 2018 Just Me | My Email <a href="mailto:me@somewhere.com">me@somewhere.com</a></div>
|
||||
<div id="designedBy" style="">
|
||||
Web Site By: <a href="http://somewhere.com" style="font-weight: normal" target="_blank">Your Company</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
181
imports/ui/layouts/Public.import.styl
vendored
Normal file
181
imports/ui/layouts/Public.import.styl
vendored
Normal file
@@ -0,0 +1,181 @@
|
||||
|
||||
#publicBody
|
||||
position: relative
|
||||
max-width: 950px
|
||||
min-width: 250px
|
||||
margin: 0 auto
|
||||
#page
|
||||
background: #FDFDFD
|
||||
@media(max-width: 549px)
|
||||
#head
|
||||
margin: 0 auto
|
||||
height: 0
|
||||
#logo
|
||||
position: absolute
|
||||
right: 5px
|
||||
top: 0
|
||||
width: 40px
|
||||
height: 40px
|
||||
background: url(images/Logo_v1.png) no-repeat top center
|
||||
background-size: 40px 40px
|
||||
cursor: pointer
|
||||
.page
|
||||
margin: 0 auto
|
||||
padding: 6px 4px
|
||||
position: relative
|
||||
#menuBackground
|
||||
height: 20px
|
||||
max-width: 950px
|
||||
background: #FFF
|
||||
#menu
|
||||
white-space: nowrap
|
||||
position: absolute
|
||||
height: 20px
|
||||
z-index: 200
|
||||
font-family: "Open Sans", Arial, Helvetica, sans-serif
|
||||
font-weight: 600
|
||||
font-size: .8em
|
||||
#menu a
|
||||
margin: 0 0 0 5px
|
||||
text-decoration: none
|
||||
color: black
|
||||
line-height: 20px
|
||||
display: inline-block
|
||||
height: 20px
|
||||
border-bottom: 1px solid transparent
|
||||
/* Force the browser to include padding and border as part of the size of the block. */
|
||||
-webkit-box-sizing: border-box /* Safari/Chrome, other WebKit */
|
||||
-moz-box-sizing: border-box /* Firefox, other Gecko */
|
||||
box-sizing: border-box /* Opera/IE 8+ */
|
||||
#menu a:hover
|
||||
opacity: .7
|
||||
color: black
|
||||
border-bottom: 1px solid red
|
||||
#links
|
||||
white-space: nowrap
|
||||
position: absolute
|
||||
right: 50px
|
||||
top: 26px
|
||||
height: 15px
|
||||
width: 60px
|
||||
text-align: left
|
||||
z-index: 100
|
||||
font-family: Arial, Helvetica, sans-serif
|
||||
font-size: .8em
|
||||
font-weight: 800
|
||||
#links a
|
||||
display: inline-block
|
||||
width: 20px
|
||||
height: 15px
|
||||
border-bottom: 1px solid transparent
|
||||
/* Force the browser to include padding and border as part of the size of the block. */
|
||||
-webkit-box-sizing: border-box /* Safari/Chrome, other WebKit */
|
||||
-moz-box-sizing: border-box /* Firefox, other Gecko */
|
||||
box-sizing: border-box /* Opera/IE 8+ */
|
||||
#links a:hover
|
||||
border-bottom: 1px solid red
|
||||
opacity: .7
|
||||
#linkFacebook
|
||||
background: url('images/Facebook_v2.png') no-repeat center center
|
||||
background-size: auto 9px
|
||||
#linkGoogle
|
||||
background: url('images/GooglePlus_v2.png') no-repeat center center
|
||||
background-size: 12px auto
|
||||
#linkTwitter
|
||||
background: url('images/Twitter_v2.png') no-repeat center center
|
||||
background-size: 12px auto
|
||||
@media(min-width: 550px)
|
||||
#head
|
||||
margin: 0 auto
|
||||
background: url(images/Header_v1.jpg) no-repeat top center
|
||||
background-size: contain
|
||||
max-width: 950px
|
||||
height: 171px
|
||||
#logo
|
||||
position: absolute
|
||||
right: 10px
|
||||
top: 10px
|
||||
width: 120px
|
||||
height: 120px
|
||||
background: url(images/Logo_v1.png) no-repeat top center
|
||||
background-size: 120px 120px
|
||||
cursor: pointer
|
||||
.page
|
||||
margin: 0 auto
|
||||
padding: 30px 20px
|
||||
position: relative
|
||||
#menuBackground
|
||||
height: 31px
|
||||
max-width: 950px
|
||||
background: #FFF
|
||||
#menu
|
||||
white-space: nowrap
|
||||
position: absolute
|
||||
height: 30px
|
||||
margin-bottom: 1px
|
||||
z-index: 200
|
||||
font-family: "Open Sans", Arial, Helvetica, sans-serif
|
||||
font-weight: 600
|
||||
font-size: 1em
|
||||
#menu a
|
||||
margin: 0 0 0 16px
|
||||
text-decoration: none
|
||||
color: black
|
||||
line-height: 30px
|
||||
display: inline-block
|
||||
height: 30px
|
||||
border-bottom: 3px solid transparent
|
||||
/* Force the browser to include padding and border as part of the size of the block. */
|
||||
-webkit-box-sizing: border-box /* Safari/Chrome, other WebKit */
|
||||
-moz-box-sizing: border-box /* Firefox, other Gecko */
|
||||
box-sizing: border-box /* Opera/IE 8+ */
|
||||
#menu a.holidayMenuItem
|
||||
font-family: "Grand Hotel", "Open Sans", Arial, Helvetica, sans-serif
|
||||
font-weight: 400
|
||||
font-size: 1.3em
|
||||
letter-spacing: 1px
|
||||
vertical-align: top
|
||||
#menu a.shippingMenuItem
|
||||
font-family: "Grand Hotel", "Open Sans", Arial, Helvetica, sans-serif
|
||||
font-weight: 400
|
||||
font-size: 1.3em
|
||||
letter-spacing: 1px
|
||||
vertical-align: top
|
||||
#menu a:hover
|
||||
opacity: 1
|
||||
color: black
|
||||
background: transparent
|
||||
border-bottom: 3px solid #a20010
|
||||
#link
|
||||
white-space: nowrap
|
||||
position: absolute
|
||||
right: 10px
|
||||
top: 130px
|
||||
height: 30px
|
||||
width: 120px
|
||||
text-align: center
|
||||
z-index: 200
|
||||
font-family: Arial, Helvetica, sans-serif
|
||||
font-weight: 800
|
||||
font-size: 1em
|
||||
#links a
|
||||
display: inline-block
|
||||
width: 30px
|
||||
height: 30px
|
||||
border-bottom: 2px solid transparent
|
||||
/* Force the browser to include padding and border as part of the size of the block. */
|
||||
-webkit-box-sizing: border-box /* Safari/Chrome, other WebKit */
|
||||
-moz-box-sizing: border-box /* Firefox, other Gecko */
|
||||
box-sizing: border-box /* Opera/IE 8+ */
|
||||
#links a:hover
|
||||
border-bottom: 2px solid rgb(200, 146, 186) /*#a20010;*/
|
||||
opacity: 1
|
||||
#linkFacebook
|
||||
background: url('images/Facebook_white_v2.png') no-repeat center 5px
|
||||
background-size: 20px auto
|
||||
#linkGoogle
|
||||
background: url('images/GooglePlus_white_v2.png') no-repeat center 6px
|
||||
background-size: 20px auto
|
||||
#linkTwitter
|
||||
background: url('images/Twitter_white_v2.png') no-repeat center 7px
|
||||
background-size: 20px auto
|
||||
6
imports/ui/layouts/Public.js
Normal file
6
imports/ui/layouts/Public.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Template } from 'meteor/templating';
|
||||
import './Public.html';
|
||||
|
||||
Template.Public.events({
|
||||
|
||||
});
|
||||
52
imports/ui/styles/buttons.import.styl
vendored
Normal file
52
imports/ui/styles/buttons.import.styl
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
|
||||
span.button
|
||||
margin: 0 0 0 1px
|
||||
padding: 0.5em 1em
|
||||
border: 1px solid #d4d4d4
|
||||
border-radius: 50em
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
overflow: hidden
|
||||
cursor: pointer
|
||||
outline: none
|
||||
background-color: #ececec
|
||||
color: #333
|
||||
font: 11px/normal sans-serif
|
||||
text-shadow: 1px 1px 0 #fff
|
||||
text-align: center
|
||||
text-decoration: none
|
||||
//Prevent selection
|
||||
-webkit-touch-callout: none; /* iOS Safari */
|
||||
-webkit-user-select: none; /* Chrome/Safari/Opera */
|
||||
-khtml-user-select: none; /* Konqueror */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||
user-select: none; /* Non-prefixed version, currently not supported by any browser */
|
||||
span.button:hover
|
||||
color: blue
|
||||
span.button:active
|
||||
color: #fff
|
||||
background-color: #141414
|
||||
text-shadow: 1px 1px 0 #000
|
||||
border: 1px solid #292929
|
||||
span.button.primary
|
||||
font-weight: 800
|
||||
span.button.selected //Use this if they are toggle buttons
|
||||
color: #fff
|
||||
background-color: #141414
|
||||
text-shadow: 1px 1px 0 #000
|
||||
cursor: default
|
||||
span.buttonGroup
|
||||
:not(:first-child):not(:last-child)
|
||||
border-radius: 0
|
||||
:first-child
|
||||
border-top-left-radius: 50em
|
||||
border-bottom-left-radius: 50em
|
||||
border-top-right-radius: 0
|
||||
border-bottom-right-radius: 0
|
||||
margin-left: 0
|
||||
:last-child
|
||||
border-top-left-radius: 0
|
||||
border-bottom-left-radius: 0
|
||||
border-top-right-radius: 50em
|
||||
border-bottom-right-radius: 50em
|
||||
52
imports/ui/styles/effects.import.styl
vendored
Normal file
52
imports/ui/styles/effects.import.styl
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
// For black text.
|
||||
@-webkit-keyframes neon6
|
||||
from
|
||||
text-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #fff, 0 0 40px #ff00de, 0 0 70px #ff00de, 0 0 80px #ff00de, 0 0 100px #ff00de, 0 0 150px #ff00de
|
||||
to
|
||||
text-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 15px #fff, 0 0 20px #ff00de, 0 0 35px #ff00de, 0 0 40px #ff00de, 0 0 50px #ff00de, 0 0 75px #ff00de
|
||||
@-moz-keyframes neon6
|
||||
from
|
||||
text-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #fff, 0 0 40px #ff00de, 0 0 70px #ff00de, 0 0 80px #ff00de, 0 0 100px #ff00de, 0 0 150px #ff00de
|
||||
to
|
||||
text-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 15px #fff, 0 0 20px #ff00de, 0 0 35px #ff00de, 0 0 40px #ff00de, 0 0 50px #ff00de, 0 0 75px #ff00de
|
||||
@keyframes neon6
|
||||
from
|
||||
text-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #fff, 0 0 40px #ff00de, 0 0 70px #ff00de, 0 0 80px #ff00de, 0 0 100px #ff00de, 0 0 150px #ff00de
|
||||
to
|
||||
text-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 15px #fff, 0 0 20px #ff00de, 0 0 35px #ff00de, 0 0 40px #ff00de, 0 0 50px #ff00de, 0 0 75px #ff00de
|
||||
|
||||
// For white text.
|
||||
@-webkit-keyframes neon7
|
||||
from
|
||||
text-shadow: 0 0 10px #bbb, 0 0 20px #bbb, 0 0 30px #bbb, 0 0 40px #ff00de, 0 0 70px #ff00de, 0 0 80px #ff00de, 0 0 100px #ff00de, 0 0 150px #ff00de
|
||||
to
|
||||
text-shadow: 0 0 5px #bbb, 0 0 10px #bbb, 0 0 15px #bbb, 0 0 20px #ff00de, 0 0 35px #ff00de, 0 0 40px #ff00de, 0 0 50px #ff00de, 0 0 75px #ff00de
|
||||
@-moz-keyframes neon7
|
||||
from
|
||||
text-shadow: 0 0 10px #bbb, 0 0 20px #bbb, 0 0 30px #bbb, 0 0 40px #ff00de, 0 0 70px #ff00de, 0 0 80px #ff00de, 0 0 100px #ff00de, 0 0 150px #ff00de
|
||||
to
|
||||
text-shadow: 0 0 5px #bbb, 0 0 10px #bbb, 0 0 15px #bbb, 0 0 20px #ff00de, 0 0 35px #ff00de, 0 0 40px #ff00de, 0 0 50px #ff00de, 0 0 75px #ff00de
|
||||
@keyframes neon7
|
||||
from
|
||||
text-shadow: 0 0 10px #bbb, 0 0 20px #bbb, 0 0 30px #bbb, 0 0 40px #ff00de, 0 0 70px #ff00de, 0 0 80px #ff00de, 0 0 100px #ff00de, 0 0 150px #ff00de
|
||||
to
|
||||
text-shadow: 0 0 5px #bbb, 0 0 10px #bbb, 0 0 15px #bbb, 0 0 20px #ff00de, 0 0 35px #ff00de, 0 0 40px #ff00de, 0 0 50px #ff00de, 0 0 75px #ff00de
|
||||
|
||||
//@-webkit-keyframes neon6_drop
|
||||
// from
|
||||
// -webkit-filter: drop-shadow(0px 0px 20px rgba(194,0,0,0.7))
|
||||
// filter: url(shadow.svg#drop-shadow)
|
||||
// 50%
|
||||
// -webkit-filter: drop-shadow(0px 0px 20px rgba(255,0,0,1))
|
||||
// filter: url(shadow.svg#drop-shadow)
|
||||
// to
|
||||
// -webkit-filter: drop-shadow(0px 0px 20px rgba(194,0,0,0.7))
|
||||
// filter: url(shadow.svg#drop-shadow)
|
||||
|
||||
@keyframes neon6_drop
|
||||
from
|
||||
filter: drop-shadow(0px 0px 20px rgba(194,0,0,0.7)) brightness(120%)
|
||||
50%
|
||||
filter: drop-shadow(0px 0px 20px rgba(255,0,0,1)) brightness(80%)
|
||||
to
|
||||
filter: drop-shadow(0px 0px 20px rgba(194,0,0,0.7)) brightness(100%)
|
||||
117
imports/ui/styles/forms.import.styl
vendored
Normal file
117
imports/ui/styles/forms.import.styl
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
|
||||
//Form Styles
|
||||
.select2-container
|
||||
font-size: 10px
|
||||
.select2-selection
|
||||
font-size: 13px //Make the font small enough the control can have a height similar to a standard input field.
|
||||
margin-bottom: 0px
|
||||
min-height: 10px !important //This is what really sets the height of the box containing the selection(s)
|
||||
padding-bottom: 2px //Add a little space below the selections to balance it all out.
|
||||
input
|
||||
padding: 6px
|
||||
border-radius: 4px
|
||||
border-width: 1px
|
||||
border-style: solid
|
||||
border-color: #ccc
|
||||
//input[type='button'].btn-success, input[type='submit'].btn-success
|
||||
// background-color: #5cb85c
|
||||
// :hover
|
||||
// background-color:
|
||||
//input[type='button'].btn-danger, input[type='submit'].btn-danger
|
||||
// background-color: #e55b46
|
||||
.form-control, .select2-selection //?
|
||||
font-size: 14px
|
||||
margin-bottom: 0px
|
||||
.form-group
|
||||
margin: 4px 0
|
||||
.has-error .form-control
|
||||
border-color: #a94442
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075)
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075)
|
||||
|
||||
@media screen and (-webkit-min-device-pixel-ratio: 0)
|
||||
input[type="date"].form-control, input[type="time"].form-control, input[type="datetime-local"].form-control, input[type="month"].form-control
|
||||
line-height: 34px
|
||||
|
||||
.form-control
|
||||
display: block
|
||||
width: 100%
|
||||
height: 34px
|
||||
padding: 6px 12px
|
||||
font-size: 14px
|
||||
line-height: 1.42857143
|
||||
color: #555
|
||||
background-color: #fff
|
||||
background-image: none
|
||||
border: 1px solid #ccc
|
||||
border-radius: 4px
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075)
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075)
|
||||
-webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s
|
||||
-o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s
|
||||
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s
|
||||
input[type="date"], input[type="datetime-local"], input[type="month"], input[type="time"], input[type="week"]
|
||||
align-items: center
|
||||
-webkit-padding-start: 1px
|
||||
overflow: hidden
|
||||
padding-left: 10px
|
||||
input
|
||||
-webkit-appearance: textfield
|
||||
background-color: white
|
||||
-webkit-rtl-ordering: logical
|
||||
user-select: text
|
||||
cursor: auto
|
||||
padding: 1px
|
||||
border-width: 2px
|
||||
border-style: inset
|
||||
border-color: initial
|
||||
border-image: initial
|
||||
.form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control
|
||||
background-color: #eee
|
||||
opacity: 1
|
||||
input, textarea, keygen, select, button
|
||||
text-rendering: auto
|
||||
color: initial
|
||||
letter-spacing: normal
|
||||
word-spacing: normal
|
||||
text-transform: none
|
||||
text-indent: 0px
|
||||
text-shadow: none
|
||||
display: inline-block
|
||||
text-align: start
|
||||
margin: 0em 0em 0em 0em
|
||||
font: 13.3333px Arial
|
||||
input, textarea, keygen, select, button, meter, progress
|
||||
-webkit-writing-mode: horizontal-tb
|
||||
//.btn.disabled, .btn[disabled], fieldset[disabled] .btn
|
||||
// cursor: not-allowed
|
||||
// filter: unquote("alpha(opacity=65)")
|
||||
// -webkit-box-shadow: none
|
||||
// box-shadow: none
|
||||
// opacity: .65
|
||||
//button, html input[type="button"], input[type="reset"], input[type="submit"]
|
||||
// -webkit-appearance: button
|
||||
// cursor: pointer
|
||||
//button, html input[type="button"], input[type="reset"], input[type="submit"]
|
||||
// -webkit-appearance: button
|
||||
// cursor: pointer
|
||||
//.btn
|
||||
// display: inline-block;
|
||||
// padding: 6px 12px;
|
||||
// margin-bottom: 0;
|
||||
// font-size: 14px;
|
||||
// font-weight: normal;
|
||||
// line-height: 1.42857143;
|
||||
// text-align: center;
|
||||
// white-space: nowrap;
|
||||
// vertical-align: middle;
|
||||
// -ms-touch-action: manipulation;
|
||||
// touch-action: manipulation;
|
||||
// cursor: pointer;
|
||||
// -webkit-user-select: none;
|
||||
// -moz-user-select: none;
|
||||
// -ms-user-select: none;
|
||||
// user-select: none;
|
||||
// background-image: none;
|
||||
// border: 1px solid transparent;
|
||||
// border-radius: 4px;
|
||||
133
imports/ui/styles/maxHeightLayout.import.styl
vendored
Normal file
133
imports/ui/styles/maxHeightLayout.import.styl
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
section.maxHeightContainer, div.maxHeightContainer
|
||||
display: table
|
||||
height: 100%
|
||||
width: 100%
|
||||
section.maxHeightRow, div.maxHeightRow
|
||||
display: table-row
|
||||
section.maxHeightContent, div.maxHeightContent //Use this for a row of content that should shrink to fit the content size.
|
||||
display: table-cell
|
||||
height: 1px
|
||||
section.maxHeightContentExpandAndScroll, div.maxHeightContentExpandAndScroll //Use this for a row of content that should take up all remaining space and will contain potentially scrolled content.
|
||||
display: table-cell
|
||||
height: 100%
|
||||
position: relative
|
||||
section.maxHeightContentScrolled, div.maxHeightContentScrolled //Use this to create the scrolled content. Can use any display within it.
|
||||
position: absolute
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
width: auto
|
||||
height: auto
|
||||
overflow-y: auto
|
||||
|
||||
// *****************
|
||||
// ** Vertical Stack
|
||||
// ** Use .verticalStack on containers, and .vscFixed or .vscExpand on children (they can also be containers).
|
||||
// ** Designed to use Flexbox to allow full screen (vertical) layouts. Fixed children will fit the content, and expand children will consume all available vertical space, but will not exceed the vertical space.
|
||||
// ** Use .columnContainer to setup a horizontally scrolling, full height container where children are tiled down first, then wrap to the next column.
|
||||
/*
|
||||
Test Code:
|
||||
-------CSS------
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
html {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
body {
|
||||
background: purple;
|
||||
color: black;
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.vscFixed {
|
||||
flex: 0 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
.vscExpand {
|
||||
flex: 1 1 1px;
|
||||
width: 100%;
|
||||
}
|
||||
.verticalStack {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
align-content: stretch;
|
||||
}
|
||||
.columnContainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
align-content: flex-start;
|
||||
overflow-x: auto;
|
||||
background: white;
|
||||
}
|
||||
.columnContent {
|
||||
flex: none;
|
||||
width: 300px;
|
||||
}
|
||||
-------Javascript------
|
||||
var container = document.querySelector('.columnContainer');
|
||||
for(var i = 0; i < 400; i++) {
|
||||
var child = document.createElement("div");
|
||||
child.innerHTML = "Element " + i;
|
||||
child.className = "columnContent";
|
||||
container.appendChild(child);
|
||||
}
|
||||
-------HTML------
|
||||
<div class="verticalStack">
|
||||
<div class='vscFixed' style="width: 100%; background:green">
|
||||
<p>
|
||||
Some content.
|
||||
</p>
|
||||
<p>
|
||||
More content...
|
||||
</p>
|
||||
</div>
|
||||
<div class="verticalStack vscExpand">
|
||||
<div class='vscFixed' style="background: yellow;">
|
||||
Test bar.
|
||||
</div>
|
||||
<div class="columnContainer vscExpand">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
*/
|
||||
.vscFixed {
|
||||
flex: 0 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
.vscExpand {
|
||||
flex: 1 1 1px;
|
||||
width: 100%;
|
||||
}
|
||||
.verticalStack {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
align-content: stretch;
|
||||
}
|
||||
.columnContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
align-content: flex-start;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.columnContent {
|
||||
flex: none;
|
||||
}
|
||||
61
imports/ui/styles/tabs.import.styl
vendored
Normal file
61
imports/ui/styles/tabs.import.styl
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
ul.tabRow
|
||||
position: relative
|
||||
text-align: left
|
||||
list-style: none
|
||||
margin: 0
|
||||
padding: 0 0 0 10px
|
||||
line-height: 24px
|
||||
height: 26px
|
||||
font-size: 12px
|
||||
overflow: hidden
|
||||
li
|
||||
position: relative
|
||||
z-index: 0
|
||||
border: 1px solid #AAA
|
||||
background: #D1D1D1
|
||||
display: inline-block
|
||||
border-top-left-radius: 6px
|
||||
border-top-right-radius: 6px
|
||||
background: -o-linear-gradient(top, #ECECEC 50%, #D1D1D1 100%)
|
||||
background: -ms-linear-gradient(top, #ECECEC 50%, #D1D1D1 100%)
|
||||
background: -moz-linear-gradient(top, #ECECEC 50%, #D1D1D1 100%)
|
||||
background: -webkit-linear-gradient(top, #ECECEC 50%, #D1D1D1 100%)
|
||||
background: linear-gradient(top, #ECECEC 50%, #D1D1D1 100%)
|
||||
box-shadow: 0 3px 3px rgba(0, 0, 0, 0.4), inset 0 1px 0 #FFF
|
||||
text-shadow: 0 1px #FFF
|
||||
margin: 0 -5px
|
||||
padding: 0 20px
|
||||
li:before, li:after
|
||||
position: absolute
|
||||
bottom: -1px
|
||||
width: 5px
|
||||
height: 5px
|
||||
content: " "
|
||||
border: 1px solid #AAA
|
||||
li:before
|
||||
left: -6px
|
||||
border-bottom-right-radius: 6px
|
||||
border-width: 0 1px 1px 0
|
||||
box-shadow: 2px 2px 0 #D1D1D1
|
||||
li:after
|
||||
right: -6px
|
||||
border-bottom-left-radius: 6px
|
||||
border-width: 0 0 1px 1px
|
||||
box-shadow: -2px 2px 0 #D1D1D1
|
||||
li.selected
|
||||
z-index: 2
|
||||
background: white
|
||||
color: #333
|
||||
border-bottom-color: #FFF
|
||||
li.selected:before
|
||||
box-shadow: 2px 2px 0 #FFF
|
||||
li.selected:after
|
||||
box-shadow: -2px 2px 0 #FFF
|
||||
ul.tabRow:before
|
||||
position: absolute
|
||||
width: 100%
|
||||
bottom: 0
|
||||
left: 0
|
||||
border-bottom: 1px solid #AAA
|
||||
z-index: 1
|
||||
content: " "
|
||||
463
imports/util/bootstrap-like-btn.import.styl
vendored
Normal file
463
imports/util/bootstrap-like-btn.import.styl
vendored
Normal file
@@ -0,0 +1,463 @@
|
||||
.btn
|
||||
display: inline-block
|
||||
padding: 6px 12px
|
||||
margin-bottom: 0
|
||||
font-size: 14px
|
||||
font-weight: normal
|
||||
line-height: 1.42857143
|
||||
text-align: center
|
||||
white-space: nowrap
|
||||
vertical-align: middle
|
||||
-ms-touch-action: manipulation
|
||||
touch-action: manipulation
|
||||
cursor: pointer
|
||||
-webkit-user-select: none
|
||||
-moz-user-select: none
|
||||
-ms-user-select: none
|
||||
user-select: none
|
||||
background-image: none
|
||||
border: 1px solid transparent
|
||||
border-radius: 4px
|
||||
|
||||
.btn:focus,
|
||||
.btn:active:focus,
|
||||
.btn.active:focus,
|
||||
.btn.focus,
|
||||
.btn:active.focus,
|
||||
.btn.active.focus
|
||||
outline: 5px auto -webkit-focus-ring-color
|
||||
outline-offset: -2px
|
||||
|
||||
.btn:hover,
|
||||
.btn:focus,
|
||||
.btn.focus
|
||||
color: #333
|
||||
text-decoration: none
|
||||
|
||||
.btn:active,
|
||||
.btn.active
|
||||
background-image: none
|
||||
outline: 0
|
||||
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125)
|
||||
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125)
|
||||
.btn.disabled,
|
||||
.btn[disabled],
|
||||
fieldset[disabled] .btn
|
||||
cursor: not-allowed
|
||||
filter: unquote("alpha(opacity=65)")
|
||||
-webkit-box-shadow: none
|
||||
box-shadow: none
|
||||
opacity: .65
|
||||
|
||||
a.btn.disabled,
|
||||
fieldset[disabled] a.btn
|
||||
pointer-events: none
|
||||
|
||||
.btn-default
|
||||
color: #333
|
||||
background-color: #fff
|
||||
border-color: #ccc
|
||||
|
||||
.btn-default:focus,
|
||||
.btn-default.focus
|
||||
color: #333
|
||||
background-color: #e6e6e6
|
||||
border-color: #8c8c8c
|
||||
|
||||
.btn-default:hover
|
||||
color: #333
|
||||
background-color: #e6e6e6
|
||||
border-color: #adadad
|
||||
|
||||
.btn-default:active,
|
||||
.btn-default.active,
|
||||
.open > .dropdown-toggle.btn-default
|
||||
color: #333
|
||||
background-color: #e6e6e6
|
||||
border-color: #adadad
|
||||
|
||||
.btn-default:active:hover,
|
||||
.btn-default.active:hover,
|
||||
.open > .dropdown-toggle.btn-default:hover,
|
||||
.btn-default:active:focus,
|
||||
.btn-default.active:focus,
|
||||
.open > .dropdown-toggle.btn-default:focus,
|
||||
.btn-default:active.focus,
|
||||
.btn-default.active.focus,
|
||||
.open > .dropdown-toggle.btn-default.focus
|
||||
color: #333
|
||||
background-color: #d4d4d4
|
||||
border-color: #8c8c8c
|
||||
|
||||
.btn-default:active,
|
||||
.btn-default.active,
|
||||
.open > .dropdown-toggle.btn-default
|
||||
background-image: none
|
||||
|
||||
.btn-default.disabled:hover,
|
||||
.btn-default[disabled]:hover,
|
||||
fieldset[disabled] .btn-default:hover,
|
||||
.btn-default.disabled:focus,
|
||||
.btn-default[disabled]:focus,
|
||||
fieldset[disabled] .btn-default:focus,
|
||||
.btn-default.disabled.focus,
|
||||
.btn-default[disabled].focus,
|
||||
fieldset[disabled] .btn-default.focus
|
||||
background-color: #fff
|
||||
border-color: #ccc
|
||||
|
||||
.btn-default .badge
|
||||
color: #fff
|
||||
background-color: #333
|
||||
|
||||
.btn-primary
|
||||
color: #fff
|
||||
background-color: #337ab7
|
||||
border-color: #2e6da4
|
||||
|
||||
.btn-primary:focus,
|
||||
.btn-primary.focus
|
||||
color: #fff
|
||||
background-color: #286090
|
||||
border-color: #122b40
|
||||
|
||||
.btn-primary:hover
|
||||
color: #fff
|
||||
background-color: #286090
|
||||
border-color: #204d74
|
||||
|
||||
.btn-primary:active,
|
||||
.btn-primary.active,
|
||||
.open > .dropdown-toggle.btn-primary
|
||||
color: #fff
|
||||
background-color: #286090
|
||||
border-color: #204d74
|
||||
|
||||
.btn-primary:active:hover,
|
||||
.btn-primary.active:hover,
|
||||
.open > .dropdown-toggle.btn-primary:hover,
|
||||
.btn-primary:active:focus,
|
||||
.btn-primary.active:focus,
|
||||
.open > .dropdown-toggle.btn-primary:focus,
|
||||
.btn-primary:active.focus,
|
||||
.btn-primary.active.focus,
|
||||
.open > .dropdown-toggle.btn-primary.focus
|
||||
color: #fff
|
||||
background-color: #204d74
|
||||
border-color: #122b40
|
||||
|
||||
.btn-primary:active,
|
||||
.btn-primary.active,
|
||||
.open > .dropdown-toggle.btn-primary
|
||||
background-image: none
|
||||
|
||||
.btn-primary.disabled:hover,
|
||||
.btn-primary[disabled]:hover,
|
||||
fieldset[disabled] .btn-primary:hover,
|
||||
.btn-primary.disabled:focus,
|
||||
.btn-primary[disabled]:focus,
|
||||
fieldset[disabled] .btn-primary:focus,
|
||||
.btn-primary.disabled.focus,
|
||||
.btn-primary[disabled].focus,
|
||||
fieldset[disabled] .btn-primary.focus
|
||||
background-color: #337ab7
|
||||
border-color: #2e6da4
|
||||
|
||||
.btn-primary .badge
|
||||
color: #337ab7
|
||||
background-color: #fff
|
||||
|
||||
.btn-success
|
||||
color: #fff
|
||||
background-color: #5cb85c
|
||||
border-color: #4cae4c
|
||||
|
||||
.btn-success:focus,
|
||||
.btn-success.focus
|
||||
color: #fff
|
||||
background-color: #449d44
|
||||
border-color: #255625
|
||||
|
||||
.btn-success:hover
|
||||
color: #fff
|
||||
background-color: #449d44
|
||||
border-color: #398439
|
||||
|
||||
.btn-success:active,
|
||||
.btn-success.active,
|
||||
.open > .dropdown-toggle.btn-success
|
||||
color: #fff
|
||||
background-color: #449d44
|
||||
border-color: #398439
|
||||
|
||||
.btn-success:active:hover,
|
||||
.btn-success.active:hover,
|
||||
.open > .dropdown-toggle.btn-success:hover,
|
||||
.btn-success:active:focus,
|
||||
.btn-success.active:focus,
|
||||
.open > .dropdown-toggle.btn-success:focus,
|
||||
.btn-success:active.focus,
|
||||
.btn-success.active.focus,
|
||||
.open > .dropdown-toggle.btn-success.focus
|
||||
color: #fff
|
||||
background-color: #398439
|
||||
border-color: #255625
|
||||
|
||||
.btn-success:active,
|
||||
.btn-success.active,
|
||||
.open > .dropdown-toggle.btn-success
|
||||
background-image: none
|
||||
|
||||
.btn-success.disabled:hover,
|
||||
.btn-success[disabled]:hover,
|
||||
fieldset[disabled] .btn-success:hover,
|
||||
.btn-success.disabled:focus,
|
||||
.btn-success[disabled]:focus,
|
||||
fieldset[disabled] .btn-success:focus,
|
||||
.btn-success.disabled.focus,
|
||||
.btn-success[disabled].focus,
|
||||
fieldset[disabled] .btn-success.focus
|
||||
background-color: #5cb85c
|
||||
border-color: #4cae4c
|
||||
|
||||
.btn-success .badge
|
||||
color: #5cb85c
|
||||
background-color: #fff
|
||||
|
||||
.btn-info
|
||||
color: #fff
|
||||
background-color: #5bc0de
|
||||
border-color: #46b8da
|
||||
|
||||
.btn-info:focus,
|
||||
.btn-info.focus
|
||||
color: #fff
|
||||
background-color: #31b0d5
|
||||
border-color: #1b6d85
|
||||
|
||||
.btn-info:hover
|
||||
color: #fff
|
||||
background-color: #31b0d5
|
||||
border-color: #269abc
|
||||
|
||||
.btn-info:active,
|
||||
.btn-info.active,
|
||||
.open > .dropdown-toggle.btn-info
|
||||
color: #fff
|
||||
background-color: #31b0d5
|
||||
border-color: #269abc
|
||||
|
||||
.btn-info:active:hover,
|
||||
.btn-info.active:hover,
|
||||
.open > .dropdown-toggle.btn-info:hover,
|
||||
.btn-info:active:focus,
|
||||
.btn-info.active:focus,
|
||||
.open > .dropdown-toggle.btn-info:focus,
|
||||
.btn-info:active.focus,
|
||||
.btn-info.active.focus,
|
||||
.open > .dropdown-toggle.btn-info.focus
|
||||
color: #fff
|
||||
background-color: #269abc
|
||||
border-color: #1b6d85
|
||||
|
||||
.btn-info:active,
|
||||
.btn-info.active,
|
||||
.open > .dropdown-toggle.btn-info
|
||||
background-image: none
|
||||
|
||||
.btn-info.disabled:hover,
|
||||
.btn-info[disabled]:hover,
|
||||
fieldset[disabled] .btn-info:hover,
|
||||
.btn-info.disabled:focus,
|
||||
.btn-info[disabled]:focus,
|
||||
fieldset[disabled] .btn-info:focus,
|
||||
.btn-info.disabled.focus,
|
||||
.btn-info[disabled].focus,
|
||||
fieldset[disabled] .btn-info.focus
|
||||
background-color: #5bc0de
|
||||
border-color: #46b8da
|
||||
|
||||
.btn-info .badge
|
||||
color: #5bc0de
|
||||
background-color: #fff
|
||||
|
||||
.btn-warning
|
||||
color: #fff
|
||||
background-color: #f0ad4e
|
||||
border-color: #eea236
|
||||
|
||||
.btn-warning:focus,
|
||||
.btn-warning.focus
|
||||
color: #fff
|
||||
background-color: #ec971f
|
||||
border-color: #985f0d
|
||||
|
||||
.btn-warning:hover
|
||||
color: #fff
|
||||
background-color: #ec971f
|
||||
border-color: #d58512
|
||||
|
||||
.btn-warning:active,
|
||||
.btn-warning.active,
|
||||
.open > .dropdown-toggle.btn-warning
|
||||
color: #fff
|
||||
background-color: #ec971f
|
||||
border-color: #d58512
|
||||
|
||||
.btn-warning:active:hover,
|
||||
.btn-warning.active:hover,
|
||||
.open > .dropdown-toggle.btn-warning:hover,
|
||||
.btn-warning:active:focus,
|
||||
.btn-warning.active:focus,
|
||||
.open > .dropdown-toggle.btn-warning:focus,
|
||||
.btn-warning:active.focus,
|
||||
.btn-warning.active.focus,
|
||||
.open > .dropdown-toggle.btn-warning.focus
|
||||
color: #fff
|
||||
background-color: #d58512
|
||||
border-color: #985f0d
|
||||
|
||||
.btn-warning:active,
|
||||
.btn-warning.active,
|
||||
.open > .dropdown-toggle.btn-warning
|
||||
background-image: none
|
||||
|
||||
.btn-warning.disabled:hover,
|
||||
.btn-warning[disabled]:hover,
|
||||
fieldset[disabled] .btn-warning:hover,
|
||||
.btn-warning.disabled:focus,
|
||||
.btn-warning[disabled]:focus,
|
||||
fieldset[disabled] .btn-warning:focus,
|
||||
.btn-warning.disabled.focus,
|
||||
.btn-warning[disabled].focus,
|
||||
fieldset[disabled] .btn-warning.focus
|
||||
background-color: #f0ad4e
|
||||
border-color: #eea236
|
||||
|
||||
.btn-warning .badge
|
||||
color: #f0ad4e
|
||||
background-color: #fff
|
||||
|
||||
.btn-danger
|
||||
color: #fff
|
||||
background-color: #d9534f
|
||||
border-color: #d43f3a
|
||||
|
||||
.btn-danger:focus,
|
||||
.btn-danger.focus
|
||||
color: #fff
|
||||
background-color: #c9302c
|
||||
border-color: #761c19
|
||||
|
||||
.btn-danger:hover
|
||||
color: #fff
|
||||
background-color: #c9302c
|
||||
border-color: #ac2925
|
||||
|
||||
.btn-danger:active,
|
||||
.btn-danger.active,
|
||||
.open > .dropdown-toggle.btn-danger
|
||||
color: #fff
|
||||
background-color: #c9302c
|
||||
border-color: #ac2925
|
||||
|
||||
.btn-danger:active:hover,
|
||||
.btn-danger.active:hover,
|
||||
.open > .dropdown-toggle.btn-danger:hover,
|
||||
.btn-danger:active:focus,
|
||||
.btn-danger.active:focus,
|
||||
.open > .dropdown-toggle.btn-danger:focus,
|
||||
.btn-danger:active.focus,
|
||||
.btn-danger.active.focus,
|
||||
.open > .dropdown-toggle.btn-danger.focus
|
||||
color: #fff
|
||||
background-color: #ac2925
|
||||
border-color: #761c19
|
||||
|
||||
.btn-danger:active,
|
||||
.btn-danger.active,
|
||||
.open > .dropdown-toggle.btn-danger
|
||||
background-image: none
|
||||
|
||||
.btn-danger.disabled:hover,
|
||||
.btn-danger[disabled]:hover,
|
||||
fieldset[disabled] .btn-danger:hover,
|
||||
.btn-danger.disabled:focus,
|
||||
.btn-danger[disabled]:focus,
|
||||
fieldset[disabled] .btn-danger:focus,
|
||||
.btn-danger.disabled.focus,
|
||||
.btn-danger[disabled].focus,
|
||||
fieldset[disabled] .btn-danger.focus
|
||||
background-color: #d9534f
|
||||
border-color: #d43f3a
|
||||
|
||||
.btn-danger .badge
|
||||
color: #d9534f
|
||||
background-color: #fff
|
||||
|
||||
.btn-link
|
||||
font-weight: normal
|
||||
color: #337ab7
|
||||
border-radius: 0
|
||||
|
||||
.btn-link,
|
||||
.btn-link:active,
|
||||
.btn-link.active,
|
||||
.btn-link[disabled],
|
||||
fieldset[disabled] .btn-link
|
||||
background-color: transparent
|
||||
-webkit-box-shadow: none
|
||||
box-shadow: none
|
||||
|
||||
.btn-link,
|
||||
.btn-link:hover,
|
||||
.btn-link:focus,
|
||||
.btn-link:active
|
||||
border-color: transparent
|
||||
|
||||
.btn-link:hover,
|
||||
.btn-link:focus
|
||||
color: #23527c
|
||||
text-decoration: underline
|
||||
background-color: transparent
|
||||
|
||||
.btn-link[disabled]:hover,
|
||||
fieldset[disabled] .btn-link:hover,
|
||||
.btn-link[disabled]:focus,
|
||||
fieldset[disabled] .btn-link:focus
|
||||
color: #777
|
||||
text-decoration: none
|
||||
|
||||
.btn-lg,
|
||||
.btn-group-lg > .btn
|
||||
padding: 10px 16px
|
||||
font-size: 18px
|
||||
line-height: 1.3333333
|
||||
border-radius: 6px
|
||||
|
||||
.btn-sm,
|
||||
.btn-group-sm > .btn
|
||||
padding: 5px 10px
|
||||
font-size: 12px
|
||||
line-height: 1.5
|
||||
border-radius: 3px
|
||||
|
||||
.btn-xs,
|
||||
.btn-group-xs > .btn
|
||||
padding: 1px 5px
|
||||
font-size: 12px
|
||||
line-height: 1.5
|
||||
border-radius: 3px
|
||||
|
||||
.btn-block
|
||||
display: block
|
||||
width: 100%
|
||||
|
||||
.btn-block + .btn-block
|
||||
margin-top: 5px
|
||||
|
||||
input[type="submit"].btn-block,
|
||||
input[type="reset"].btn-block,
|
||||
input[type="button"].btn-block
|
||||
width: 100%
|
||||
|
||||
11
imports/util/csv.js
Normal file
11
imports/util/csv.js
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
let fs = require("fs");
|
||||
let csv = require('csv-parse');
|
||||
let result = function(csvFilePath, callback) {
|
||||
let parser = csv(Assets.getText(csvFilePath), {delimiter: '\t'}, function(error, data) {
|
||||
if(error) callback(error);
|
||||
else callback(null, data);
|
||||
});
|
||||
};
|
||||
|
||||
export default result;
|
||||
31
imports/util/de.combo.import.styl
vendored
Normal file
31
imports/util/de.combo.import.styl
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
.comboList {
|
||||
z-index: 1000;
|
||||
max-height: 160px;
|
||||
overflow-y: auto;
|
||||
-moz-box-shadow: 0 2px 3px #ccc;
|
||||
-webkit-box-shadow: 0 2px 3px #ccc;
|
||||
box-shadow: 0 2px 3px #ccc;
|
||||
border: 1px solid #d1d1d1;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: none;
|
||||
background: white;
|
||||
|
||||
li {
|
||||
display: block;
|
||||
padding: 5px 10px;
|
||||
margin: 0;
|
||||
text-indent: 0
|
||||
background: white;
|
||||
}
|
||||
li.selected {
|
||||
background-color: #ffe184 !important;
|
||||
}
|
||||
li[role='node'] {
|
||||
font-weight: 800;
|
||||
}
|
||||
li[role='leaf'] {
|
||||
padding-left: 2em;
|
||||
}
|
||||
}
|
||||
455
imports/util/de.combo.js
Normal file
455
imports/util/de.combo.js
Normal file
@@ -0,0 +1,455 @@
|
||||
"use strict";
|
||||
|
||||
//
|
||||
// Takes a input form element and a hidden form element (to store the selected id in) along with an array of objects, to build a dropdown select control that allows the user to type part of the selection to filter the list.
|
||||
// Modified for meteor.
|
||||
//
|
||||
// @param options: See Combo.DEFAULTS below.
|
||||
//
|
||||
//
|
||||
(function($) {
|
||||
let Combo = function($input, $hidden, options) {
|
||||
let _this = this;
|
||||
|
||||
this.focusCounter = 0;
|
||||
this.$input = $input;
|
||||
this.$hidden = $hidden;
|
||||
this.options = $.extend({}, Combo.DEFAULTS, options);
|
||||
this.$selected = null;
|
||||
this.$listContainer = $('<div/>', {style: 'position: relative; height: 0;'});
|
||||
this.$list = $('<ul/>', {role: 'menu', class: this.options.listClass});
|
||||
|
||||
//Ensure that if the hidden field exists and changes, that the hidden field's id matches the text in the input field. If not then the hidden id field was changed manually and externally and the text field should be updated.
|
||||
if(this.$hidden) {
|
||||
this.$hidden.on('change', hiddenInputChanged);
|
||||
}
|
||||
|
||||
function hiddenInputChanged() {
|
||||
let id = _this.$hidden.val();
|
||||
let $li = _this.$list.children("[role!='node']");
|
||||
|
||||
for(let i = 0; i < $li.length; i++) {
|
||||
let $next = $($li[i]);
|
||||
|
||||
if($next.data('model').id == id) {
|
||||
if(_this.$input.val() != $next.text())
|
||||
_this.$input.val($next.text());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//this.$list.appendTo($input.parent());
|
||||
this.$list.appendTo(this.$listContainer);
|
||||
//this.$listContainer.appendTo($input.parent());
|
||||
this.$listContainer.prependTo(document.body); //Place the container at the top of the page with no height.
|
||||
|
||||
//Setup the list to highlight the item the user is hovering over, to select the item the user clicks, and to remove the hover styling when the list closes due to a selection being made.
|
||||
this.$list
|
||||
.on('mousemove', 'li', function() {
|
||||
// _this.$list.find(_this.options.selectionClass).removeClass(_this.options.selectionClass);
|
||||
let $this = $(this);
|
||||
|
||||
//Skip nodes.
|
||||
while($this && $this.attr('role') == 'node') {
|
||||
$this = $this.next();
|
||||
}
|
||||
|
||||
//If we could find a non-node element then highlight it.
|
||||
if($this) $this.addClass(_this.options.selectionClass).siblings().removeClass(_this.options.selectionClass);
|
||||
})
|
||||
.on('mousedown', 'li', function() {
|
||||
let $this = $(this);
|
||||
|
||||
//Skip nodes.
|
||||
while($this && $this.attr('role') == 'node') {
|
||||
$this = $this.next();
|
||||
}
|
||||
|
||||
//If we could find a non-node element then highlight it.
|
||||
if($this) _this.select($this);
|
||||
})
|
||||
.on('mouseup', 'li', function() {
|
||||
//Remove the selection highlighting.
|
||||
_this.$list.children().removeClass(_this.options.selectionClass);
|
||||
//Hide the list.
|
||||
_this.hide();
|
||||
});
|
||||
//Setup the input field to handle opening the list when it receives focus, and close it when it loses focus.
|
||||
this.$input
|
||||
.on('focus', $.proxy(_this.focus, _this))
|
||||
.on('blur', $.proxy(_this.blur, _this));
|
||||
// this.$listContainer
|
||||
// .on('focus', $.proxy(_this.focus, _this, "list container"))
|
||||
// .on('blur', $.proxy(_this.blur, _this, "list container"));
|
||||
// this.$list
|
||||
// .on('focus', $.proxy(_this.focus, _this, "list"))
|
||||
// .on('blur', $.proxy(_this.blur, _this, "list"));
|
||||
//Handle key events on the input field. Up/down arrows should change the selection in the list. Enter should select an item and close the list. Tab and escape should hide the list before moving to the next focusable element on the page.
|
||||
this.$input.on('input keydown', function(event) {
|
||||
switch(event.keyCode) {
|
||||
case 38: { //Up
|
||||
let visibles = _this.$list.find('li.visible[role!="node"]');
|
||||
let selected = visibles.index(visibles.filter('.' + _this.options.selectionClass)) || 0;
|
||||
_this.highlight(selected - 1);
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
case 40: { //Down
|
||||
let visibles = _this.$list.find('li.visible[role!="node"]');
|
||||
let selected = visibles.index(visibles.filter('li.selected')) || 0;
|
||||
_this.highlight(selected + 1);
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
case 13: //Enter
|
||||
if(_this.$list.is(':visible')) {
|
||||
_this.select(_this.$list.find('li.selected'));
|
||||
event.preventDefault();
|
||||
}
|
||||
break;
|
||||
case 9: //Tab
|
||||
_this.select(_this.$list.find('li.selected'));
|
||||
break;
|
||||
case 27: //Esc
|
||||
if(_this.$input.hasClass('open')) {
|
||||
_this.hide();
|
||||
//Try to stop any default behavior from occurring.
|
||||
if(event.stopPropagation) event.stopPropagation();
|
||||
else event.cancelBubble = true; //IE 6-8
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
_this.filter();
|
||||
_this.highlight(0);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Adds one or more elements to the control.
|
||||
// data: The item or array of items to add. This will be the root tree elements if groupFunctions is provided.
|
||||
function add(data) {
|
||||
let groupFunctions = _this.options.groupFunctions;
|
||||
let getClasses = _this.options.getClasses;
|
||||
|
||||
let addOne = function(data, parent) {
|
||||
let text = _this.options.textAttr ? ($.isFunction(_this.options.textAttr) ? _this.options.textAttr(data) : data[_this.options.textAttr]) : data;
|
||||
let li = $("<li" + (parent ? " role='leaf'" : "") + (getClasses ? " class='" + getClasses(data) + "'" : "") + ">" + text + "</li>");
|
||||
|
||||
li.appendTo(_this.$list);
|
||||
li.data('model', data);
|
||||
if(parent) li.data('parent-li', parent);
|
||||
};
|
||||
let addOneGroup = function(data, text, children) {
|
||||
let li = $("<li role='node'>" + text + "</li>");
|
||||
|
||||
li.appendTo(_this.$list);
|
||||
li.data('model', data);
|
||||
|
||||
for(let childIndex = 0; childIndex < children.length; childIndex++) {
|
||||
addOne(children[childIndex], li);
|
||||
}
|
||||
};
|
||||
let addOneBranch = function(data) {
|
||||
let parents = $.isFunction(groupFunctions.groupParents) ? groupFunctions.groupParents(data) : data;
|
||||
|
||||
//Since there may be one or more parents identified for each data element passed to us...
|
||||
if(Array.isArray(parents)) {
|
||||
for(let parentIndex = 0; parentIndex < parents.length; parentIndex++) {
|
||||
addOneGroup(parents[parentIndex], groupFunctions.parentText(parents[parentIndex]), groupFunctions.children(parents[parentIndex]));
|
||||
}
|
||||
}
|
||||
else {
|
||||
addOneGroup(parents, groupFunctions.parentText(parents), groupFunctions.children(parents));
|
||||
}
|
||||
};
|
||||
|
||||
if(groupFunctions instanceof Object && $.isFunction(groupFunctions.children) && $.isFunction(groupFunctions.parentText)) {
|
||||
if(Array.isArray(data)) {
|
||||
for(let dataIndex = 0; dataIndex < data.length; dataIndex++) {
|
||||
addOneBranch(data[dataIndex]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
addOneBranch(data);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(Array.isArray(data)) {
|
||||
for(let dataIndex = 0; dataIndex < data.length; dataIndex++) {
|
||||
addOne(data[dataIndex]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
addOne(data);
|
||||
}
|
||||
}
|
||||
|
||||
//Filter the set of elements so that only those matching the text in the input field are marked as visible.
|
||||
_this.filter();
|
||||
}
|
||||
|
||||
Tracker.autorun(function() {
|
||||
this.$list.empty();
|
||||
if(options.cursor) {
|
||||
//Add the initial set of data.
|
||||
add(options.cursor.fetch());
|
||||
}
|
||||
else if(options.set) {
|
||||
for(let i = 0; i < options.set.length; i++) {
|
||||
add(options.set[i]);
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
//Check the hidden input field for an ID, and setup the selection based in it if there is one.
|
||||
if(this.$hidden && _this.$hidden.val()) {
|
||||
hiddenInputChanged();
|
||||
}
|
||||
|
||||
//TODO: Should probably check to ensure comparator is a function? Not that it will help much if the function is not written correctly. Stupid Javascript!
|
||||
if(this.options.selection && this.options.comparator) {
|
||||
Tracker.autorun(function() {
|
||||
let selectedData = this.options.selection.get();
|
||||
|
||||
if(selectedData) {
|
||||
let listItems = this.$list.children();
|
||||
let found = false;
|
||||
|
||||
for(let i = 0; !found && i < listItems.length; i++) {
|
||||
if(this.options.comparator($(listItems[i]).data('model'), selectedData)) {
|
||||
found = true;
|
||||
this.select($(listItems[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
Combo.DEFAULTS = {
|
||||
cursor: undefined, //A meteor Cursor used to populate the values displayed in the combo.
|
||||
set: [], //An array of values displayed in the combo. This must be specified if cursor is not specified.
|
||||
selection: undefined, //A meteor ReactiveVar whose value will be set to the current selection.
|
||||
comparator: function(a, b) {return a === b;}, //A function that takes two collection objects and compares them for equality. If the combo shows users for example, this comparator would compare one user id to another. Required for the combo to set the selection if the view changes it externally relative to this combo.
|
||||
textAttr: undefined, //The attribute of the data elements to use for the name. This can also be a function that takes the data object and returns the text.
|
||||
idAttr: 'id', //The attribute of the data elements to use for the ID. This can also be a function that takes the data obejct and returns the ID.
|
||||
// groupFunctions: The object containing three functions: 'groupParent', 'parentText', 'children'.
|
||||
// groupParents(data) will take a data element and return the objects that best represents the parents of the children (for a multi layer tree, this would be the node just before the leaf nodes).
|
||||
// parentText(parent) will be passed the group parent and the data object that generated it, and will return the text that represents the path to that parent.
|
||||
// children(parent) will be passed the group parent (returned by groupParents()), and will return an array of children or leaf nodes for the tree.
|
||||
groupFunctions: undefined,
|
||||
filter: true, //Whether to filter the list as the user types.
|
||||
effects: 'fade',
|
||||
duration: '200',
|
||||
listClass: 'de.combo-list',
|
||||
selectionClass: 'selected', //The class to use for the selected element in the dropdown list.
|
||||
getClasses: undefined //An optional function that will return a string to use in the list item's class attribute to style the list item for a given model data. The function will be passed the data object for the list item.
|
||||
};
|
||||
|
||||
Combo.prototype.select = function($li) {
|
||||
if($li.length == 0) {
|
||||
if(this.$input.val() != '') {
|
||||
this.$input.val("");
|
||||
if(this.$hidden) this.$hidden.val(undefined).change();
|
||||
this.filter();
|
||||
//Note: Don't trigger the select event - for some reason it causes the dropdown to reopen and the control to retain focus when clicking out of the widget.
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(!this.$list.has($li) || !$li.is('li.visible')) return;
|
||||
|
||||
//No need to change selection if the selection has not changed.
|
||||
if(this.$input.val() != $li.text()) {
|
||||
this.$input.val($li.text()); //Save the selected text into the text input.
|
||||
if(this.$hidden) {
|
||||
this.$hidden.val($li.data('model')[this.options.idAttr]);
|
||||
this.$hidden.change();
|
||||
} //Save the ID into the hidden form input if it exists.
|
||||
this.hide();
|
||||
this.filter();
|
||||
//this.trigger('select', $li);
|
||||
|
||||
//Set the reactive var for the selection if one is provided and the selection has changed relative to the model.
|
||||
if(this.options.selection && this.options.selection.get() != $li.data('model')) {
|
||||
this.options.selection.set($li.data('model'));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Combo.prototype.escapeRegex = function(s) {
|
||||
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
};
|
||||
|
||||
// Removes all filtering. This is used to clear the filtering when first opening the combo when there is a value in the field. This is desirable because when we have an exact match, but are opening the combo, we most often want to select a new value.
|
||||
Combo.prototype.clearFilter = function() {
|
||||
console.log("CLearing Filter");
|
||||
//Show all list elements.
|
||||
this.$list.find('li').addClass('visible').show();
|
||||
//Hide any node list elements.
|
||||
this.$list.find('li[role="node"]').removeClass('visible').hide();
|
||||
};
|
||||
|
||||
//Filters the list items by marking those that match the text in the text field as having the class 'visible'.
|
||||
Combo.prototype.filter = function() {
|
||||
try {
|
||||
let search = this.$input.val();
|
||||
let _this = this;
|
||||
|
||||
search = search ? search : "";
|
||||
search = search.toLowerCase().trim();
|
||||
|
||||
//Show all list elements.
|
||||
this.$list.find('li').addClass('visible').show();
|
||||
//Hide any node list elements.
|
||||
this.$list.find('li[role="node"]').removeClass('visible').hide();
|
||||
|
||||
if(this.options.filter) {
|
||||
//Hide non-node elements (leaf nodes) that don't match.
|
||||
let li = this.$list.children();
|
||||
|
||||
let searches = search && search.length > 0 ? search.split(/\s+/) : undefined;
|
||||
let regexs = searches ? [] : undefined;
|
||||
|
||||
if(searches) {
|
||||
for(let i = 0; i < searches.length; i++) {
|
||||
regexs.push(new RegExp("\\b" + this.escapeRegex(searches[i])));
|
||||
}
|
||||
}
|
||||
|
||||
//Iterate over the list elements:
|
||||
// hide all list items that are nodes;
|
||||
// show all list items that are not nodes and whose text matches the input value;
|
||||
// show all node list items associated with visible child list items (they occur after the parent, so the parent will be hidden first, then made visible).
|
||||
for(let i = 0; i < li.length; i++) {
|
||||
let $next = $(li[i]);
|
||||
let node = $next.attr('role') == 'node';
|
||||
//let hidden = node || $next.text().toLowerCase().indexOf(search) < 0;
|
||||
let text = $next.text().toLowerCase();
|
||||
let match = true;
|
||||
|
||||
if(!node && searches) {
|
||||
for(let i = 0; match && i < regexs.length; i++) {
|
||||
match = regexs[i].test(text)
|
||||
}
|
||||
}
|
||||
|
||||
//let match = text.match(/\bxxx/gi);
|
||||
let hidden = node || !match;
|
||||
|
||||
if(hidden) $next.removeClass('visible').hide();
|
||||
|
||||
//If this isn't hidden and we have a tree with grouping, then turn on the group (parent) associated with this option.
|
||||
if(!hidden && _this.options.groupFunctions) {
|
||||
let parent = $next.data('parent-li');
|
||||
|
||||
if(!parent.hasClass('visible')) parent.addClass('visible').show();
|
||||
}
|
||||
}
|
||||
|
||||
//If we hid all elements then hide the whole list.
|
||||
if(this.$list.find('li.visible').length == 0) this.hide();
|
||||
}
|
||||
} catch(e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
Combo.prototype.focus = function() {
|
||||
this.show();
|
||||
this.$input.select();
|
||||
};
|
||||
|
||||
Combo.prototype.blur = function() {
|
||||
this.hide();
|
||||
this.select(this.$list.find('li.selected'));
|
||||
};
|
||||
|
||||
Combo.prototype.show = function() {
|
||||
// Make sure we don't repeatedly try to show the combo.
|
||||
if(!this.isShowing) {
|
||||
let position = this.$input.offset();
|
||||
|
||||
this.isShowing = true;
|
||||
// Position the list relative to the field. Note that we place the combo at the top of the page (in the body tag) to avoid overflow not showing and to ensure the page scrolls if needed.
|
||||
this.$list.css({position: 'absolute', top: position.top + this.$input.outerHeight(), left: position.left, width: this.$input.outerWidth()});
|
||||
this.clearFilter();
|
||||
|
||||
if(!this.$list.is(':visible') && this.$list.find('li.visible').length > 0) {
|
||||
let fns = {default: 'show', fade: 'fadeIn', slide: 'slideDown'};
|
||||
let fn = fns[this.options.effects];
|
||||
|
||||
this.trigger('show');
|
||||
this.$input.addClass('open');
|
||||
this.$list[fn](this.options.duration, $.proxy(this.trigger, this, 'shown'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Combo.prototype.hide = function() {
|
||||
if(this.isShowing) {
|
||||
let fns = {default: 'hide', fade: 'fadeOut', slide: 'slideUp'};
|
||||
let fn = fns[this.options.effects];
|
||||
|
||||
this.isShowing = false;
|
||||
this.trigger('hide');
|
||||
this.$input.removeClass('open');
|
||||
this.$list[fn](this.options.duration, $.proxy(this.trigger, this, 'hidden'));
|
||||
}
|
||||
};
|
||||
|
||||
// goDown: true/false - defaults to true - indicating whether the highlighting should go up or down if the requested item is a node. Nodes cannot be highlighted or selected.
|
||||
Combo.prototype.highlight = function(index) {
|
||||
let _this = this;
|
||||
|
||||
this.show();
|
||||
|
||||
setTimeout(function() {
|
||||
let visibles = _this.$list.find('li.visible[role!="node"]');
|
||||
let oldSelected = _this.$list.find('li.' + _this.options.selectionClass).removeClass(_this.options.selectionClass);
|
||||
let oldSelectedIndex = visibles.index(oldSelected);
|
||||
|
||||
if(visibles.length > 0) {
|
||||
let selectedIndex = (visibles.length + index) % visibles.length;
|
||||
let selected = visibles.eq(selectedIndex);
|
||||
let top = selected.position().top;
|
||||
|
||||
if(selected.attr('role') != 'node') selected.addClass(_this.options.selectionClass);
|
||||
|
||||
if(selectedIndex < oldSelectedIndex && top < 0)
|
||||
_this.$list.scrollTop(_this.$list.scrollTop() + top);
|
||||
if(selectedIndex > oldSelectedIndex && top + selected.outerHeight() > _this.$list.outerHeight())
|
||||
_this.$list.scrollTop(_this.$list.scrollTop() + selected.outerHeight() + 2 * (top - _this.$list.outerHeight()));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Combo.prototype.trigger = function(event) {
|
||||
let params = Array.prototype.slice.call(arguments, 1);
|
||||
let args = [event + '.de.combo'];
|
||||
|
||||
args.push(params);
|
||||
|
||||
if(this.$select) this.$select.trigger.apply(this.$select, args);
|
||||
this.$input.trigger.apply(this.$input, args);
|
||||
};
|
||||
|
||||
$.fn.buildCombo = function(options) {
|
||||
for(let index = 0; index < this.length; index++) {
|
||||
let $next = $(this[index]);
|
||||
let nextCombo = new Combo($next, $next.siblings('input[type=hidden]').first(), options);
|
||||
|
||||
$next.data("de.combo", nextCombo);
|
||||
}
|
||||
};
|
||||
$.fn.getCombo = function() {
|
||||
if(this.length > 0) {
|
||||
return $(this[0]).data('de.combo');
|
||||
}
|
||||
};
|
||||
})(jQuery);
|
||||
508
imports/util/normalize.css
vendored
Normal file
508
imports/util/normalize.css
vendored
Normal file
@@ -0,0 +1,508 @@
|
||||
/*! normalize.css 2012-03-11T12:53 UTC - http://github.com/necolas/normalize.css */
|
||||
|
||||
/* =============================================================================
|
||||
HTML5 display definitions
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Corrects block display not defined in IE6/7/8/9 & FF3
|
||||
*/
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Corrects inline-block display not defined in IE6/7/8/9 & FF3
|
||||
*/
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
video {
|
||||
display: inline-block;
|
||||
*display: inline;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prevents modern browsers from displaying 'audio' without controls
|
||||
* Remove excess height in iOS5 devices
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling for 'hidden' attribute not present in IE7/8/9, FF3, S4
|
||||
* Known issue: no IE6 support
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Base
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* 1. Corrects text resizing oddly in IE6/7 when body font-size is set using em units
|
||||
* http://clagnut.com/blog/348/#c790
|
||||
* 2. Prevents iOS text size adjust after orientation change, without disabling user zoom
|
||||
* www.456bereastreet.com/archive/201012/controlling_text_size_in_safari_for_ios_without_disabling_user_zoom/
|
||||
*/
|
||||
|
||||
html {
|
||||
font-size: 100%; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses font-family inconsistency between 'textarea' and other form elements.
|
||||
*/
|
||||
|
||||
html,
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses margins handled incorrectly in IE6/7
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Links
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses outline displayed oddly in Chrome
|
||||
*/
|
||||
|
||||
a:focus {
|
||||
outline: thin dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
* Improves readability when focused and also mouse hovered in all browsers
|
||||
* people.opera.com/patrickl/experiments/keyboard/test
|
||||
*/
|
||||
|
||||
a:hover,
|
||||
a:active {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Typography
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses font sizes and margins set differently in IE6/7
|
||||
* Addresses font sizes within 'section' and 'article' in FF4+, Chrome, S5
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5em;
|
||||
margin: 0.83em 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.17em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1em;
|
||||
margin: 1.33em 0;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 0.83em;
|
||||
margin: 1.67em 0;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 0.75em;
|
||||
margin: 2.33em 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling not present in IE7/8/9, S5, Chrome
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses style set to 'bolder' in FF3+, S4/5, Chrome
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling not present in S5, Chrome
|
||||
*/
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling not present in IE6/7/8/9
|
||||
*/
|
||||
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses margins set differently in IE6/7
|
||||
*/
|
||||
|
||||
p,
|
||||
pre {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Corrects font family set oddly in IE6, S4/5, Chrome
|
||||
* en.wikipedia.org/wiki/User:Davidgothberg/Test59
|
||||
*/
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, serif;
|
||||
_font-family: 'courier new', monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/*
|
||||
* Improves readability of pre-formatted text in all browsers
|
||||
*/
|
||||
|
||||
pre {
|
||||
white-space: pre;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Addresses CSS quotes not supported in IE6/7
|
||||
* 2. Addresses quote property not supported in S4
|
||||
*/
|
||||
|
||||
/* 1 */
|
||||
|
||||
q {
|
||||
quotes: none;
|
||||
}
|
||||
|
||||
/* 2 */
|
||||
|
||||
q:before,
|
||||
q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prevents sub and sup affecting line-height in all browsers
|
||||
* gist.github.com/413930
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Lists
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses margins set differently in IE6/7
|
||||
*/
|
||||
|
||||
dl,
|
||||
menu,
|
||||
ol,
|
||||
ul {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin: 0 0 0 40px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses paddings set differently in IE6/7
|
||||
*/
|
||||
|
||||
menu,
|
||||
ol,
|
||||
ul {
|
||||
padding: 0 0 0 40px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Corrects list images handled incorrectly in IE7
|
||||
*/
|
||||
|
||||
nav ul,
|
||||
nav ol {
|
||||
list-style: none;
|
||||
list-style-image: none;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* 1. Removes border when inside 'a' element in IE6/7/8/9, FF3
|
||||
* 2. Improves image quality when scaled in IE7
|
||||
* code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/
|
||||
*/
|
||||
|
||||
img {
|
||||
border: 0; /* 1 */
|
||||
-ms-interpolation-mode: bicubic; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Corrects overflow displayed oddly in IE9
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Figures
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses margin not present in IE6/7/8/9, S5, O11
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 0;
|
||||
-webkit-margin-before: 0;
|
||||
-webkit-margin-after: 0;
|
||||
-webkit-margin-start: 0;
|
||||
-webkit-margin-end: 0;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Forms
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Corrects margin displayed oddly in IE6/7
|
||||
*/
|
||||
|
||||
form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Define consistent border, margin, and padding
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Corrects color not being inherited in IE6/7/8/9
|
||||
* 2. Corrects text not wrapping in FF3
|
||||
* 3. Corrects alignment displayed oddly in IE6/7
|
||||
*/
|
||||
|
||||
legend {
|
||||
border: 0; /* 1 */
|
||||
padding: 0;
|
||||
white-space: normal; /* 2 */
|
||||
*margin-left: -7px; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Corrects font size not being inherited in all browsers
|
||||
* 2. Addresses margins set differently in IE6/7, FF3+, S5, Chrome
|
||||
* 3. Improves appearance and consistency in all browsers
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font-size: 100%; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
vertical-align: baseline; /* 3 */
|
||||
*vertical-align: middle; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses FF3/4 setting line-height on 'input' using !important in the UA stylesheet
|
||||
*/
|
||||
|
||||
button,
|
||||
input {
|
||||
line-height: normal; /* 1 */
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Improves usability and consistency of cursor style between image-type 'input' and others
|
||||
* 2. Corrects inability to style clickable 'input' types in iOS
|
||||
* 3. Removes inner spacing in IE7 without affecting normal text inputs
|
||||
* Known issue: inner spacing remains in IE6
|
||||
*/
|
||||
|
||||
button,
|
||||
input[type="button"],
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
cursor: pointer; /* 1 */
|
||||
-webkit-appearance: button; /* 2 */
|
||||
*overflow: visible; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-set default cursor for disabled elements
|
||||
*/
|
||||
|
||||
button[disabled],
|
||||
input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Addresses box sizing set to content-box in IE8/9
|
||||
* 2. Removes excess padding in IE8/9
|
||||
* 3. Removes excess padding in IE7
|
||||
Known issue: excess padding remains in IE6
|
||||
*/
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
*height: 13px; /* 3 */
|
||||
*width: 13px; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Addresses appearance set to searchfield in S5, Chrome
|
||||
* 2. Addresses box-sizing set to border-box in S5, Chrome (include -moz to future-proof)
|
||||
*/
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box; /* 2 */
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes inner padding and search cancel button in S5, Chrome on OS X
|
||||
*/
|
||||
|
||||
input[type="search"]::-webkit-search-decoration,
|
||||
input[type="search"]::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes inner padding and border in FF3+
|
||||
* www.sitepen.com/blog/2008/05/14/the-devils-in-the-details-fixing-dojos-toolbar-buttons/
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Removes default vertical scrollbar in IE6/7/8/9
|
||||
* 2. Improves readability and alignment in all browsers
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto; /* 1 */
|
||||
vertical-align: top; /* 2 */
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Tables
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Remove most spacing between table cells
|
||||
*/
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
108
imports/util/polyfills/array.js
Normal file
108
imports/util/polyfills/array.js
Normal file
@@ -0,0 +1,108 @@
|
||||
// https://tc39.github.io/ecma262/#sec-array.prototype.includes
|
||||
if(!Array.prototype.includes) {
|
||||
Object.defineProperty(Array.prototype, 'includes', {
|
||||
value: function(searchElement, fromIndex) {
|
||||
|
||||
// 1. Let O be ? ToObject(this value).
|
||||
if (this == null) {
|
||||
throw new TypeError('"this" is null or not defined');
|
||||
}
|
||||
|
||||
let o = Object(this);
|
||||
|
||||
// 2. Let len be ? ToLength(? Get(O, "length")).
|
||||
let len = o.length >>> 0;
|
||||
|
||||
// 3. If len is 0, return false.
|
||||
if (len === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. Let n be ? ToInteger(fromIndex).
|
||||
// (If fromIndex is undefined, this step produces the value 0.)
|
||||
let n = fromIndex | 0;
|
||||
|
||||
// 5. If n ≥ 0, then
|
||||
// a. Let k be n.
|
||||
// 6. Else n < 0,
|
||||
// a. Let k be len + n.
|
||||
// b. If k < 0, let k be 0.
|
||||
let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
|
||||
|
||||
// 7. Repeat, while k < len
|
||||
while (k < len) {
|
||||
// a. Let elementK be the result of ? Get(O, ! ToString(k)).
|
||||
// b. If SameValueZero(searchElement, elementK) is true, return true.
|
||||
// c. Increase k by 1.
|
||||
// NOTE: === provides the correct "SameValueZero" comparison needed here.
|
||||
if (o[k] === searchElement) {
|
||||
return true;
|
||||
}
|
||||
k++;
|
||||
}
|
||||
|
||||
// 8. Return false
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//http://stackoverflow.com/questions/5306680/move-an-array-element-from-one-array-position-to-another
|
||||
if(!Array.prototype.move) {
|
||||
Array.prototype.move = function (old_index, new_index) {
|
||||
if (new_index >= this.length) {
|
||||
let k = new_index - this.length;
|
||||
while ((k--) + 1) {
|
||||
this.push(undefined);
|
||||
}
|
||||
}
|
||||
this.splice(new_index, 0, this.splice(old_index, 1)[0]);
|
||||
return this; // for testing purposes
|
||||
};
|
||||
}
|
||||
|
||||
//My own implementation to work around Javascript's shitty naming and support for collection operations.
|
||||
if(!Array.prototype.remove) {
|
||||
Array.prototype.remove = function(item) {
|
||||
let index = this.indexOf(item);
|
||||
|
||||
if(index != -1) {
|
||||
this.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//My own implementation to work around Javascript's shitty naming and support for collection operations.
|
||||
// index is optional
|
||||
if(!Array.prototype.add) {
|
||||
Array.prototype.add = function(item, index) {
|
||||
if(index == undefined || isNaN(index) || index >= this.length) return this.push(item);
|
||||
else return this.splice(index, 0, item);
|
||||
}
|
||||
}
|
||||
|
||||
//My own implementation to work around Javascript's shitty naming and support for collection operations.
|
||||
// Sorts a contiguous section of the array.
|
||||
// Index is the index of the first element to be sorted (inclusive).
|
||||
// Length is the number of elements of the array to be sorted (must be >= 2). If the length + index is greater than the array length then it will be adjusted to the end of the array.
|
||||
// All other invalid inputs will result in no sorting action taken and no error.
|
||||
if(!Array.prototype.partialSort) {
|
||||
Array.prototype.partialSort = function(index, length, compareFunction) {
|
||||
if(index >= 0 && length >= 2 && index <= (this.length - 2)) {
|
||||
//Adjust the length so it doesn't over-run the array. This is the only error correction we will perform.
|
||||
if(index + length > this.length) length = this.length - index;
|
||||
|
||||
//Shallow copy of the data in the segment to be sorted.
|
||||
let sorted = this.slice(index, length + index);
|
||||
|
||||
sorted.sort(compareFunction);
|
||||
|
||||
//Put the sorted array elements back into the array.
|
||||
for(let i = index, j = 0; j < length; i++, j++) {
|
||||
this[i] = sorted[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
imports/util/polyfills/blaze.js
Normal file
19
imports/util/polyfills/blaze.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Get the parent template instance.
|
||||
* @param {Number} [levels] How many levels to go up. Default is 1
|
||||
* @returns {Blaze.TemplateInstance}
|
||||
*/
|
||||
Blaze.TemplateInstance.prototype.parentTemplate = Blaze.TemplateInstance.prototype.parentInstance = function(levels) {
|
||||
let view = this.view;
|
||||
|
||||
levels = (typeof levels === "undefined") ? 1 : levels;
|
||||
|
||||
while(view) {
|
||||
//if(view.name.substring(0, 9) === "Template." && !(levels--)) {
|
||||
if(view.template && !(levels--)) {
|
||||
//return view.templateInstance();
|
||||
return view.templateInstance();
|
||||
}
|
||||
view = view.parentView;
|
||||
}
|
||||
};
|
||||
71
imports/util/polyfills/date.js
Normal file
71
imports/util/polyfills/date.js
Normal file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// Add a method to get a timezone adjusted date for an input field that is a date picker.
|
||||
// Use $('input[name="date"]').val(new Date().toDateInputValue()) to set the date of the input field.
|
||||
//
|
||||
Date.prototype.toDateInputValue = (function() {
|
||||
let local = new Date(this);
|
||||
local.setMinutes(this.getMinutes() - this.getTimezoneOffset());
|
||||
return local.toJSON().slice(0,10);
|
||||
});
|
||||
|
||||
Date.prototype.getWeek = function() {
|
||||
let dowOffset = 1; // I am fixing this to indicate that the first day of the week is always Monday (weeks end on Sunday), for this application. This was a parameter in the original code.
|
||||
|
||||
//dowOffset = typeof(dowOffset) == 'number' ? dowOffset : 0; //default dowOffset to zero - This should check to ensure that dowOffset is between 0..6
|
||||
let newYear = new Date(this.getFullYear(),0,1);
|
||||
let day = newYear.getDay() - dowOffset; //the day of week the year begins on
|
||||
day = (day >= 0 ? day : day + 7);
|
||||
// The number of days from the beginning of the year to this.day
|
||||
let daynum = Math.floor((this.getTime() - newYear.getTime() - (this.getTimezoneOffset()-newYear.getTimezoneOffset())*60000)/86400000) + 1;
|
||||
let weeknum;
|
||||
|
||||
// I have removed the mid-week starting cutoff detection because in this app we always want to start with week #1 (never have a week zero).
|
||||
//if(day < 4) { //if the year starts before the middle of a week
|
||||
weeknum = Math.floor((daynum + day - 1) / 7) + 1;
|
||||
|
||||
// I have turned off the detection of whether the last days of the year belong to this year's last week or next year's first week. This gets too confusing and does not result in any additional usefulness.
|
||||
//if(weeknum > 52) {
|
||||
// nYear = new Date(this.getFullYear() + 1, 0, 1);
|
||||
// nday = nYear.getDay() - dowOffset;
|
||||
// nday = nday >= 0 ? nday : nday + 7;
|
||||
// // if the next year starts before the middle of the week, it is week #1 of that year
|
||||
// weeknum = nday < 4 ? 1 : 53;
|
||||
//}
|
||||
//}
|
||||
//else {
|
||||
// weeknum = Math.floor((daynum+day-1)/7);
|
||||
//}
|
||||
|
||||
return weeknum;
|
||||
};
|
||||
|
||||
Date.prototype.getUTCWeek = function() {
|
||||
let dowOffset = 1; // I am fixing this to indicate that the first day of the week is always Monday (weeks end on Sunday), for this application. This was a parameter in the original code.
|
||||
|
||||
//dowOffset = typeof(dowOffset) == 'number' ? dowOffset : 0; //default dowOffset to zero - This should check to ensure that dowOffset is between 0..6
|
||||
let newYear = new Date(this.getUTCFullYear(),0,1);
|
||||
let day = newYear.getDay() - dowOffset; //the day of week the year begins on
|
||||
day = (day >= 0 ? day : day + 7);
|
||||
// The number of days from the beginning of the year to this.day
|
||||
let daynum = Math.floor((this.getTime() - newYear.getTime() - (this.getTimezoneOffset() - newYear.getTimezoneOffset())*60000)/86400000) + 1;
|
||||
let weeknum;
|
||||
|
||||
// I have removed the mid-week starting cutoff detection because in this app we always want to start with week #1 (never have a week zero).
|
||||
//if(day < 4) { //if the year starts before the middle of a week
|
||||
weeknum = Math.floor((daynum + day - 1) / 7) + 1;
|
||||
|
||||
// I have turned off the detection of whether the last days of the year belong to this year's last week or next year's first week. This gets too confusing and does not result in any additional usefulness.
|
||||
//if(weeknum > 52) {
|
||||
// nYear = new Date(this.getFullYear() + 1, 0, 1);
|
||||
// nday = nYear.getDay() - dowOffset;
|
||||
// nday = nday >= 0 ? nday : nday + 7;
|
||||
// // if the next year starts before the middle of the week, it is week #1 of that year
|
||||
// weeknum = nday < 4 ? 1 : 53;
|
||||
//}
|
||||
//}
|
||||
//else {
|
||||
// weeknum = Math.floor((daynum+day-1)/7);
|
||||
//}
|
||||
|
||||
return weeknum;
|
||||
};
|
||||
3
imports/util/polyfills/regex.js
Normal file
3
imports/util/polyfills/regex.js
Normal file
@@ -0,0 +1,3 @@
|
||||
RegExp.escape = function(s) {
|
||||
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
};
|
||||
515
imports/util/resize/ElementQueries.js
Normal file
515
imports/util/resize/ElementQueries.js
Normal file
@@ -0,0 +1,515 @@
|
||||
/**
|
||||
* Copyright Marc J. Schmidt. See the LICENSE file at the top-level
|
||||
* directory of this distribution and at
|
||||
* https://github.com/marcj/css-element-queries/blob/master/LICENSE.
|
||||
*/
|
||||
;
|
||||
(function (root, factory) {
|
||||
if (typeof define === "function" && define.amd) {
|
||||
define(['./ResizeSensor.js'], factory);
|
||||
} else if (typeof exports === "object") {
|
||||
module.exports = factory(require('./ResizeSensor.js'));
|
||||
} else {
|
||||
root.ElementQueries = factory(root.ResizeSensor);
|
||||
}
|
||||
}(this, function (ResizeSensor) {
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {Function}
|
||||
* @constructor
|
||||
*/
|
||||
var ElementQueries = function() {
|
||||
|
||||
var trackingActive = false;
|
||||
var elements = [];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param element
|
||||
* @returns {Number}
|
||||
*/
|
||||
function getEmSize(element) {
|
||||
if (!element) {
|
||||
element = document.documentElement;
|
||||
}
|
||||
var fontSize = window.getComputedStyle(element, null).fontSize;
|
||||
return parseFloat(fontSize) || 16;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @copyright https://github.com/Mr0grog/element-query/blob/master/LICENSE
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @param {*} value
|
||||
* @returns {*}
|
||||
*/
|
||||
function convertToPx(element, value) {
|
||||
var numbers = value.split(/\d/);
|
||||
var units = numbers[numbers.length-1];
|
||||
value = parseFloat(value);
|
||||
switch (units) {
|
||||
case "px":
|
||||
return value;
|
||||
case "em":
|
||||
return value * getEmSize(element);
|
||||
case "rem":
|
||||
return value * getEmSize();
|
||||
// Viewport units!
|
||||
// According to http://quirksmode.org/mobile/tableViewport.html
|
||||
// documentElement.clientWidth/Height gets us the most reliable info
|
||||
case "vw":
|
||||
return value * document.documentElement.clientWidth / 100;
|
||||
case "vh":
|
||||
return value * document.documentElement.clientHeight / 100;
|
||||
case "vmin":
|
||||
case "vmax":
|
||||
var vw = document.documentElement.clientWidth / 100;
|
||||
var vh = document.documentElement.clientHeight / 100;
|
||||
var chooser = Math[units === "vmin" ? "min" : "max"];
|
||||
return value * chooser(vw, vh);
|
||||
default:
|
||||
return value;
|
||||
// for now, not supporting physical units (since they are just a set number of px)
|
||||
// or ex/ch (getting accurate measurements is hard)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @constructor
|
||||
*/
|
||||
function SetupInformation(element) {
|
||||
this.element = element;
|
||||
this.options = {};
|
||||
var key, option, width = 0, height = 0, value, actualValue, attrValues, attrValue, attrName;
|
||||
|
||||
/**
|
||||
* @param {Object} option {mode: 'min|max', property: 'width|height', value: '123px'}
|
||||
*/
|
||||
this.addOption = function(option) {
|
||||
var idx = [option.mode, option.property, option.value].join(',');
|
||||
this.options[idx] = option;
|
||||
};
|
||||
|
||||
var attributes = ['min-width', 'min-height', 'max-width', 'max-height'];
|
||||
|
||||
/**
|
||||
* Extracts the computed width/height and sets to min/max- attribute.
|
||||
*/
|
||||
this.call = function() {
|
||||
// extract current dimensions
|
||||
width = this.element.offsetWidth;
|
||||
height = this.element.offsetHeight;
|
||||
|
||||
attrValues = {};
|
||||
|
||||
for (key in this.options) {
|
||||
if (!this.options.hasOwnProperty(key)){
|
||||
continue;
|
||||
}
|
||||
option = this.options[key];
|
||||
|
||||
value = convertToPx(this.element, option.value);
|
||||
|
||||
actualValue = option.property == 'width' ? width : height;
|
||||
attrName = option.mode + '-' + option.property;
|
||||
attrValue = '';
|
||||
|
||||
if (option.mode == 'min' && actualValue >= value) {
|
||||
attrValue += option.value;
|
||||
}
|
||||
|
||||
if (option.mode == 'max' && actualValue <= value) {
|
||||
attrValue += option.value;
|
||||
}
|
||||
|
||||
if (!attrValues[attrName]) attrValues[attrName] = '';
|
||||
if (attrValue && -1 === (' '+attrValues[attrName]+' ').indexOf(' ' + attrValue + ' ')) {
|
||||
attrValues[attrName] += ' ' + attrValue;
|
||||
}
|
||||
}
|
||||
|
||||
for (var k in attributes) {
|
||||
if(!attributes.hasOwnProperty(k)) continue;
|
||||
|
||||
if (attrValues[attributes[k]]) {
|
||||
this.element.setAttribute(attributes[k], attrValues[attributes[k]].substr(1));
|
||||
} else {
|
||||
this.element.removeAttribute(attributes[k]);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} element
|
||||
* @param {Object} options
|
||||
*/
|
||||
function setupElement(element, options) {
|
||||
if (element.elementQueriesSetupInformation) {
|
||||
element.elementQueriesSetupInformation.addOption(options);
|
||||
} else {
|
||||
element.elementQueriesSetupInformation = new SetupInformation(element);
|
||||
element.elementQueriesSetupInformation.addOption(options);
|
||||
element.elementQueriesSensor = new ResizeSensor(element, function() {
|
||||
element.elementQueriesSetupInformation.call();
|
||||
});
|
||||
}
|
||||
element.elementQueriesSetupInformation.call();
|
||||
|
||||
if (trackingActive && elements.indexOf(element) < 0) {
|
||||
elements.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} selector
|
||||
* @param {String} mode min|max
|
||||
* @param {String} property width|height
|
||||
* @param {String} value
|
||||
*/
|
||||
var allQueries = {};
|
||||
function queueQuery(selector, mode, property, value) {
|
||||
if (typeof(allQueries[mode]) == 'undefined') allQueries[mode] = {};
|
||||
if (typeof(allQueries[mode][property]) == 'undefined') allQueries[mode][property] = {};
|
||||
if (typeof(allQueries[mode][property][value]) == 'undefined') allQueries[mode][property][value] = selector;
|
||||
else allQueries[mode][property][value] += ','+selector;
|
||||
}
|
||||
|
||||
function getQuery() {
|
||||
var query;
|
||||
if (document.querySelectorAll) query = document.querySelectorAll.bind(document);
|
||||
if (!query && 'undefined' !== typeof $$) query = $$;
|
||||
if (!query && 'undefined' !== typeof jQuery) query = jQuery;
|
||||
|
||||
if (!query) {
|
||||
throw 'No document.querySelectorAll, jQuery or Mootools\'s $$ found.';
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the magic. Go through all collected rules (readRules()) and attach the resize-listener.
|
||||
*/
|
||||
function findElementQueriesElements() {
|
||||
var query = getQuery();
|
||||
|
||||
for (var mode in allQueries) if (allQueries.hasOwnProperty(mode)) {
|
||||
|
||||
for (var property in allQueries[mode]) if (allQueries[mode].hasOwnProperty(property)) {
|
||||
for (var value in allQueries[mode][property]) if (allQueries[mode][property].hasOwnProperty(value)) {
|
||||
var elements = query(allQueries[mode][property][value]);
|
||||
for (var i = 0, j = elements.length; i < j; i++) {
|
||||
setupElement(elements[i], {
|
||||
mode: mode,
|
||||
property: property,
|
||||
value: value
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
*/
|
||||
function attachResponsiveImage(element) {
|
||||
var children = [];
|
||||
var rules = [];
|
||||
var sources = [];
|
||||
var defaultImageId = 0;
|
||||
var lastActiveImage = -1;
|
||||
var loadedImages = [];
|
||||
|
||||
for (var i in element.children) {
|
||||
if(!element.children.hasOwnProperty(i)) continue;
|
||||
|
||||
if (element.children[i].tagName && element.children[i].tagName.toLowerCase() === 'img') {
|
||||
children.push(element.children[i]);
|
||||
|
||||
var minWidth = element.children[i].getAttribute('min-width') || element.children[i].getAttribute('data-min-width');
|
||||
//var minHeight = element.children[i].getAttribute('min-height') || element.children[i].getAttribute('data-min-height');
|
||||
var src = element.children[i].getAttribute('data-src') || element.children[i].getAttribute('url');
|
||||
|
||||
sources.push(src);
|
||||
|
||||
var rule = {
|
||||
minWidth: minWidth
|
||||
};
|
||||
|
||||
rules.push(rule);
|
||||
|
||||
if (!minWidth) {
|
||||
defaultImageId = children.length - 1;
|
||||
element.children[i].style.display = 'block';
|
||||
} else {
|
||||
element.children[i].style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastActiveImage = defaultImageId;
|
||||
|
||||
function check() {
|
||||
var imageToDisplay = false, i;
|
||||
|
||||
for (i in children){
|
||||
if(!children.hasOwnProperty(i)) continue;
|
||||
|
||||
if (rules[i].minWidth) {
|
||||
if (element.offsetWidth > rules[i].minWidth) {
|
||||
imageToDisplay = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!imageToDisplay) {
|
||||
//no rule matched, show default
|
||||
imageToDisplay = defaultImageId;
|
||||
}
|
||||
|
||||
if (lastActiveImage != imageToDisplay) {
|
||||
//image change
|
||||
|
||||
if (!loadedImages[imageToDisplay]){
|
||||
//image has not been loaded yet, we need to load the image first in memory to prevent flash of
|
||||
//no content
|
||||
|
||||
var image = new Image();
|
||||
image.onload = function() {
|
||||
children[imageToDisplay].src = sources[imageToDisplay];
|
||||
|
||||
children[lastActiveImage].style.display = 'none';
|
||||
children[imageToDisplay].style.display = 'block';
|
||||
|
||||
loadedImages[imageToDisplay] = true;
|
||||
|
||||
lastActiveImage = imageToDisplay;
|
||||
};
|
||||
|
||||
image.src = sources[imageToDisplay];
|
||||
} else {
|
||||
children[lastActiveImage].style.display = 'none';
|
||||
children[imageToDisplay].style.display = 'block';
|
||||
lastActiveImage = imageToDisplay;
|
||||
}
|
||||
} else {
|
||||
//make sure for initial check call the .src is set correctly
|
||||
children[imageToDisplay].src = sources[imageToDisplay];
|
||||
}
|
||||
}
|
||||
|
||||
element.resizeSensor = new ResizeSensor(element, check);
|
||||
check();
|
||||
|
||||
if (trackingActive) {
|
||||
elements.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
function findResponsiveImages(){
|
||||
var query = getQuery();
|
||||
|
||||
var elements = query('[data-responsive-image],[responsive-image]');
|
||||
for (var i = 0, j = elements.length; i < j; i++) {
|
||||
attachResponsiveImage(elements[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var regex = /,?[\s\t]*([^,\n]*?)((?:\[[\s\t]*?(?:min|max)-(?:width|height)[\s\t]*?[~$\^]?=[\s\t]*?"[^"]*?"[\s\t]*?])+)([^,\n\s\{]*)/mgi;
|
||||
var attrRegex = /\[[\s\t]*?(min|max)-(width|height)[\s\t]*?[~$\^]?=[\s\t]*?"([^"]*?)"[\s\t]*?]/mgi;
|
||||
/**
|
||||
* @param {String} css
|
||||
*/
|
||||
function extractQuery(css) {
|
||||
var match;
|
||||
var smatch;
|
||||
css = css.replace(/'/g, '"');
|
||||
while (null !== (match = regex.exec(css))) {
|
||||
smatch = match[1] + match[3];
|
||||
attrs = match[2];
|
||||
|
||||
while (null !== (attrMatch = attrRegex.exec(attrs))) {
|
||||
queueQuery(smatch, attrMatch[1], attrMatch[2], attrMatch[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CssRule[]|String} rules
|
||||
*/
|
||||
function readRules(rules) {
|
||||
var selector = '';
|
||||
if (!rules) {
|
||||
return;
|
||||
}
|
||||
if ('string' === typeof rules) {
|
||||
rules = rules.toLowerCase();
|
||||
if (-1 !== rules.indexOf('min-width') || -1 !== rules.indexOf('max-width')) {
|
||||
extractQuery(rules);
|
||||
}
|
||||
} else {
|
||||
for (var i = 0, j = rules.length; i < j; i++) {
|
||||
if (1 === rules[i].type) {
|
||||
selector = rules[i].selectorText || rules[i].cssText;
|
||||
if (-1 !== selector.indexOf('min-height') || -1 !== selector.indexOf('max-height')) {
|
||||
extractQuery(selector);
|
||||
}else if(-1 !== selector.indexOf('min-width') || -1 !== selector.indexOf('max-width')) {
|
||||
extractQuery(selector);
|
||||
}
|
||||
} else if (4 === rules[i].type) {
|
||||
readRules(rules[i].cssRules || rules[i].rules);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var defaultCssInjected = false;
|
||||
|
||||
/**
|
||||
* Searches all css rules and setups the event listener to all elements with element query rules..
|
||||
*
|
||||
* @param {Boolean} withTracking allows and requires you to use detach, since we store internally all used elements
|
||||
* (no garbage collection possible if you don not call .detach() first)
|
||||
*/
|
||||
this.init = function(withTracking) {
|
||||
trackingActive = typeof withTracking === 'undefined' ? false : withTracking;
|
||||
|
||||
for (var i = 0, j = document.styleSheets.length; i < j; i++) {
|
||||
try {
|
||||
readRules(document.styleSheets[i].cssRules || document.styleSheets[i].rules || document.styleSheets[i].cssText);
|
||||
} catch(e) {
|
||||
if (e.name !== 'SecurityError') {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!defaultCssInjected) {
|
||||
var style = document.createElement('style');
|
||||
style.type = 'text/css';
|
||||
style.innerHTML = '[responsive-image] > img, [data-responsive-image] {overflow: hidden; padding: 0; } [responsive-image] > img, [data-responsive-image] > img { width: 100%;}';
|
||||
document.getElementsByTagName('head')[0].appendChild(style);
|
||||
defaultCssInjected = true;
|
||||
}
|
||||
|
||||
findElementQueriesElements();
|
||||
findResponsiveImages();
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Boolean} withTracking allows and requires you to use detach, since we store internally all used elements
|
||||
* (no garbage collection possible if you don not call .detach() first)
|
||||
*/
|
||||
this.update = function(withTracking) {
|
||||
this.init(withTracking);
|
||||
};
|
||||
|
||||
this.detach = function() {
|
||||
if (!this.withTracking) {
|
||||
throw 'withTracking is not enabled. We can not detach elements since we don not store it.' +
|
||||
'Use ElementQueries.withTracking = true; before domready or call ElementQueryes.update(true).';
|
||||
}
|
||||
|
||||
var element;
|
||||
while (element = elements.pop()) {
|
||||
ElementQueries.detach(element);
|
||||
}
|
||||
|
||||
elements = [];
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Boolean} withTracking allows and requires you to use detach, since we store internally all used elements
|
||||
* (no garbage collection possible if you don not call .detach() first)
|
||||
*/
|
||||
ElementQueries.update = function(withTracking) {
|
||||
ElementQueries.instance.update(withTracking);
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes all sensor and elementquery information from the element.
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
*/
|
||||
ElementQueries.detach = function(element) {
|
||||
if (element.elementQueriesSetupInformation) {
|
||||
//element queries
|
||||
element.elementQueriesSensor.detach();
|
||||
delete element.elementQueriesSetupInformation;
|
||||
delete element.elementQueriesSensor;
|
||||
|
||||
} else if (element.resizeSensor) {
|
||||
//responsive image
|
||||
|
||||
element.resizeSensor.detach();
|
||||
delete element.resizeSensor;
|
||||
} else {
|
||||
//console.log('detached already', element);
|
||||
}
|
||||
};
|
||||
|
||||
ElementQueries.withTracking = false;
|
||||
|
||||
ElementQueries.init = function() {
|
||||
if (!ElementQueries.instance) {
|
||||
ElementQueries.instance = new ElementQueries();
|
||||
}
|
||||
|
||||
ElementQueries.instance.init(ElementQueries.withTracking);
|
||||
};
|
||||
|
||||
var domLoaded = function (callback) {
|
||||
/* Internet Explorer */
|
||||
/*@cc_on
|
||||
@if (@_win32 || @_win64)
|
||||
document.write('<script id="ieScriptLoad" defer src="//:"><\/script>');
|
||||
document.getElementById('ieScriptLoad').onreadystatechange = function() {
|
||||
if (this.readyState == 'complete') {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
@end @*/
|
||||
/* Mozilla, Chrome, Opera */
|
||||
if (document.addEventListener) {
|
||||
document.addEventListener('DOMContentLoaded', callback, false);
|
||||
}
|
||||
/* Safari, iCab, Konqueror */
|
||||
else if (/KHTML|WebKit|iCab/i.test(navigator.userAgent)) {
|
||||
var DOMLoadTimer = setInterval(function () {
|
||||
if (/loaded|complete/i.test(document.readyState)) {
|
||||
callback();
|
||||
clearInterval(DOMLoadTimer);
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
/* Other web browsers */
|
||||
else window.onload = callback;
|
||||
};
|
||||
|
||||
ElementQueries.listen = function() {
|
||||
domLoaded(ElementQueries.init);
|
||||
};
|
||||
|
||||
// make available to common module loader
|
||||
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
||||
module.exports = ElementQueries;
|
||||
}
|
||||
else {
|
||||
window.ElementQueries = ElementQueries;
|
||||
ElementQueries.listen();
|
||||
}
|
||||
|
||||
return ElementQueries;
|
||||
|
||||
}));
|
||||
227
imports/util/resize/ResizeSensor.js
Normal file
227
imports/util/resize/ResizeSensor.js
Normal file
@@ -0,0 +1,227 @@
|
||||
/**
|
||||
* Copyright Marc J. Schmidt. See the LICENSE file at the top-level
|
||||
* directory of this distribution and at
|
||||
* https://github.com/marcj/css-element-queries/blob/master/LICENSE.
|
||||
*/
|
||||
;
|
||||
(function (root, factory) {
|
||||
if (typeof define === "function" && define.amd) {
|
||||
define(factory);
|
||||
} else if (typeof exports === "object") {
|
||||
module.exports = factory();
|
||||
} else {
|
||||
root.ResizeSensor = factory();
|
||||
}
|
||||
}(this, function () {
|
||||
|
||||
//Make sure it does not throw in a SSR (Server Side Rendering) situation
|
||||
if (typeof window === "undefined") {
|
||||
return null;
|
||||
}
|
||||
// Only used for the dirty checking, so the event callback count is limted to max 1 call per fps per sensor.
|
||||
// In combination with the event based resize sensor this saves cpu time, because the sensor is too fast and
|
||||
// would generate too many unnecessary events.
|
||||
var requestAnimationFrame = window.requestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
function (fn) {
|
||||
return window.setTimeout(fn, 20);
|
||||
};
|
||||
|
||||
/**
|
||||
* Iterate over each of the provided element(s).
|
||||
*
|
||||
* @param {HTMLElement|HTMLElement[]} elements
|
||||
* @param {Function} callback
|
||||
*/
|
||||
function forEachElement(elements, callback){
|
||||
var elementsType = Object.prototype.toString.call(elements);
|
||||
var isCollectionTyped = ('[object Array]' === elementsType
|
||||
|| ('[object NodeList]' === elementsType)
|
||||
|| ('[object HTMLCollection]' === elementsType)
|
||||
|| ('[object Object]' === elementsType)
|
||||
|| ('undefined' !== typeof jQuery && elements instanceof jQuery) //jquery
|
||||
|| ('undefined' !== typeof Elements && elements instanceof Elements) //mootools
|
||||
);
|
||||
var i = 0, j = elements.length;
|
||||
if (isCollectionTyped) {
|
||||
for (; i < j; i++) {
|
||||
callback(elements[i]);
|
||||
}
|
||||
} else {
|
||||
callback(elements);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for dimension change detection.
|
||||
*
|
||||
* @param {Element|Element[]|Elements|jQuery} element
|
||||
* @param {Function} callback
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
var ResizeSensor = function(element, callback) {
|
||||
/**
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function EventQueue() {
|
||||
var q = [];
|
||||
this.add = function(ev) {
|
||||
q.push(ev);
|
||||
};
|
||||
|
||||
var i, j;
|
||||
this.call = function() {
|
||||
for (i = 0, j = q.length; i < j; i++) {
|
||||
q[i].call();
|
||||
}
|
||||
};
|
||||
|
||||
this.remove = function(ev) {
|
||||
var newQueue = [];
|
||||
for(i = 0, j = q.length; i < j; i++) {
|
||||
if(q[i] !== ev) newQueue.push(q[i]);
|
||||
}
|
||||
q = newQueue;
|
||||
}
|
||||
|
||||
this.length = function() {
|
||||
return q.length;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} element
|
||||
* @param {String} prop
|
||||
* @returns {String|Number}
|
||||
*/
|
||||
function getComputedStyle(element, prop) {
|
||||
if (element.currentStyle) {
|
||||
return element.currentStyle[prop];
|
||||
} else if (window.getComputedStyle) {
|
||||
return window.getComputedStyle(element, null).getPropertyValue(prop);
|
||||
} else {
|
||||
return element.style[prop];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @param {Function} resized
|
||||
*/
|
||||
function attachResizeEvent(element, resized) {
|
||||
if (!element.resizedAttached) {
|
||||
element.resizedAttached = new EventQueue();
|
||||
element.resizedAttached.add(resized);
|
||||
} else if (element.resizedAttached) {
|
||||
element.resizedAttached.add(resized);
|
||||
return;
|
||||
}
|
||||
|
||||
element.resizeSensor = document.createElement('div');
|
||||
element.resizeSensor.className = 'resize-sensor';
|
||||
var style = 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: hidden; z-index: -1; visibility: hidden;';
|
||||
var styleChild = 'position: absolute; left: 0; top: 0; transition: 0s;';
|
||||
|
||||
element.resizeSensor.style.cssText = style;
|
||||
element.resizeSensor.innerHTML =
|
||||
'<div class="resize-sensor-expand" style="' + style + '">' +
|
||||
'<div style="' + styleChild + '"></div>' +
|
||||
'</div>' +
|
||||
'<div class="resize-sensor-shrink" style="' + style + '">' +
|
||||
'<div style="' + styleChild + ' width: 200%; height: 200%"></div>' +
|
||||
'</div>';
|
||||
element.appendChild(element.resizeSensor);
|
||||
|
||||
if (getComputedStyle(element, 'position') == 'static') {
|
||||
element.style.position = 'relative';
|
||||
}
|
||||
|
||||
var expand = element.resizeSensor.childNodes[0];
|
||||
var expandChild = expand.childNodes[0];
|
||||
var shrink = element.resizeSensor.childNodes[1];
|
||||
var dirty, rafId, newWidth, newHeight;
|
||||
var lastWidth = element.offsetWidth;
|
||||
var lastHeight = element.offsetHeight;
|
||||
|
||||
var reset = function() {
|
||||
expandChild.style.width = '100000px';
|
||||
expandChild.style.height = '100000px';
|
||||
|
||||
expand.scrollLeft = 100000;
|
||||
expand.scrollTop = 100000;
|
||||
|
||||
shrink.scrollLeft = 100000;
|
||||
shrink.scrollTop = 100000;
|
||||
};
|
||||
|
||||
reset();
|
||||
|
||||
var onResized = function() {
|
||||
rafId = 0;
|
||||
|
||||
if (!dirty) return;
|
||||
|
||||
lastWidth = newWidth;
|
||||
lastHeight = newHeight;
|
||||
|
||||
if (element.resizedAttached) {
|
||||
element.resizedAttached.call();
|
||||
}
|
||||
};
|
||||
|
||||
var onScroll = function() {
|
||||
newWidth = element.offsetWidth;
|
||||
newHeight = element.offsetHeight;
|
||||
dirty = newWidth != lastWidth || newHeight != lastHeight;
|
||||
|
||||
if (dirty && !rafId) {
|
||||
rafId = requestAnimationFrame(onResized);
|
||||
}
|
||||
|
||||
reset();
|
||||
};
|
||||
|
||||
var addEvent = function(el, name, cb) {
|
||||
if (el.attachEvent) {
|
||||
el.attachEvent('on' + name, cb);
|
||||
} else {
|
||||
el.addEventListener(name, cb);
|
||||
}
|
||||
};
|
||||
|
||||
addEvent(expand, 'scroll', onScroll);
|
||||
addEvent(shrink, 'scroll', onScroll);
|
||||
}
|
||||
|
||||
forEachElement(element, function(elem){
|
||||
attachResizeEvent(elem, callback);
|
||||
});
|
||||
|
||||
this.detach = function(ev) {
|
||||
ResizeSensor.detach(element, ev);
|
||||
};
|
||||
};
|
||||
|
||||
ResizeSensor.detach = function(element, ev) {
|
||||
forEachElement(element, function(elem){
|
||||
if(elem.resizedAttached && typeof ev == "function"){
|
||||
elem.resizedAttached.remove(ev);
|
||||
if(elem.resizedAttached.length()) return;
|
||||
}
|
||||
if (elem.resizeSensor) {
|
||||
if (elem.contains(elem.resizeSensor)) {
|
||||
elem.removeChild(elem.resizeSensor);
|
||||
}
|
||||
delete elem.resizeSensor;
|
||||
delete elem.resizedAttached;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return ResizeSensor;
|
||||
|
||||
}));
|
||||
7
imports/util/scrollWidth.js
Normal file
7
imports/util/scrollWidth.js
Normal file
@@ -0,0 +1,7 @@
|
||||
var scrollBarWidth = getScrollBarWidth();
|
||||
function getScrollBarWidth() {
|
||||
let $outer = $('<div>').css({visibility: 'hidden', width: 100, overflow: 'scroll'}).appendTo('body'),
|
||||
widthWithScroll = $('<div>').css({width: '100%'}).appendTo($outer).outerWidth();
|
||||
$outer.remove();
|
||||
return 100 - widthWithScroll;
|
||||
}
|
||||
484
imports/util/select2/select2.css
vendored
Normal file
484
imports/util/select2/select2.css
vendored
Normal file
@@ -0,0 +1,484 @@
|
||||
.select2-container {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
vertical-align: middle; }
|
||||
.select2-container .select2-selection--single {
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
height: 28px;
|
||||
user-select: none;
|
||||
-webkit-user-select: none; }
|
||||
.select2-container .select2-selection--single .select2-selection__rendered {
|
||||
display: block;
|
||||
padding-left: 8px;
|
||||
padding-right: 20px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap; }
|
||||
.select2-container .select2-selection--single .select2-selection__clear {
|
||||
position: relative; }
|
||||
.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered {
|
||||
padding-right: 8px;
|
||||
padding-left: 20px; }
|
||||
.select2-container .select2-selection--multiple {
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
min-height: 32px;
|
||||
user-select: none;
|
||||
-webkit-user-select: none; }
|
||||
.select2-container .select2-selection--multiple .select2-selection__rendered {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
padding-left: 8px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap; }
|
||||
.select2-container .select2-search--inline {
|
||||
float: left; }
|
||||
.select2-container .select2-search--inline .select2-search__field {
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
font-size: 100%;
|
||||
margin-top: 5px;
|
||||
padding: 0; }
|
||||
.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none; }
|
||||
|
||||
.select2-dropdown {
|
||||
background-color: white;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -100000px;
|
||||
width: 100%;
|
||||
z-index: 1051; }
|
||||
|
||||
.select2-results {
|
||||
display: block; }
|
||||
|
||||
.select2-results__options {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0; }
|
||||
|
||||
.select2-results__option {
|
||||
padding: 6px;
|
||||
user-select: none;
|
||||
-webkit-user-select: none; }
|
||||
.select2-results__option[aria-selected] {
|
||||
cursor: pointer; }
|
||||
|
||||
.select2-container--open .select2-dropdown {
|
||||
left: 0; }
|
||||
|
||||
.select2-container--open .select2-dropdown--above {
|
||||
border-bottom: none;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0; }
|
||||
|
||||
.select2-container--open .select2-dropdown--below {
|
||||
border-top: none;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0; }
|
||||
|
||||
.select2-search--dropdown {
|
||||
display: block;
|
||||
padding: 4px; }
|
||||
.select2-search--dropdown .select2-search__field {
|
||||
padding: 4px;
|
||||
width: 100%;
|
||||
box-sizing: border-box; }
|
||||
.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none; }
|
||||
.select2-search--dropdown.select2-search--hide {
|
||||
display: none; }
|
||||
|
||||
.select2-close-mask {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: block;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
min-height: 100%;
|
||||
min-width: 100%;
|
||||
height: auto;
|
||||
width: auto;
|
||||
opacity: 0;
|
||||
z-index: 99;
|
||||
background-color: #fff;
|
||||
filter: alpha(opacity=0); }
|
||||
|
||||
.select2-hidden-accessible {
|
||||
border: 0 !important;
|
||||
clip: rect(0 0 0 0) !important;
|
||||
height: 1px !important;
|
||||
margin: -1px !important;
|
||||
overflow: hidden !important;
|
||||
padding: 0 !important;
|
||||
position: absolute !important;
|
||||
width: 1px !important; }
|
||||
|
||||
.select2-container--default .select2-selection--single {
|
||||
background-color: #fff;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__rendered {
|
||||
color: #444;
|
||||
line-height: 28px; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__clear {
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
font-weight: bold; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__placeholder {
|
||||
color: #999; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__arrow {
|
||||
height: 26px;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
width: 20px; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__arrow b {
|
||||
border-color: #888 transparent transparent transparent;
|
||||
border-style: solid;
|
||||
border-width: 5px 4px 0 4px;
|
||||
height: 0;
|
||||
left: 50%;
|
||||
margin-left: -4px;
|
||||
margin-top: -2px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 0; }
|
||||
|
||||
.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear {
|
||||
float: left; }
|
||||
|
||||
.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow {
|
||||
left: 1px;
|
||||
right: auto; }
|
||||
|
||||
.select2-container--default.select2-container--disabled .select2-selection--single {
|
||||
background-color: #eee;
|
||||
cursor: default; }
|
||||
.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear {
|
||||
display: none; }
|
||||
|
||||
.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b {
|
||||
border-color: transparent transparent #888 transparent;
|
||||
border-width: 0 4px 5px 4px; }
|
||||
|
||||
.select2-container--default .select2-selection--multiple {
|
||||
background-color: white;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px;
|
||||
cursor: text; }
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__rendered {
|
||||
box-sizing: border-box;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0 5px;
|
||||
width: 100%; }
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__rendered li {
|
||||
list-style: none; }
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__placeholder {
|
||||
color: #999;
|
||||
margin-top: 5px;
|
||||
float: left; }
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__clear {
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
font-weight: bold;
|
||||
margin-top: 5px;
|
||||
margin-right: 10px; }
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__choice {
|
||||
background-color: #e4e4e4;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px;
|
||||
cursor: default;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
margin-top: 5px;
|
||||
padding: 0 5px; }
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
margin-right: 2px; }
|
||||
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
|
||||
color: #333; }
|
||||
|
||||
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline {
|
||||
float: right; }
|
||||
|
||||
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
|
||||
margin-left: 5px;
|
||||
margin-right: auto; }
|
||||
|
||||
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
|
||||
margin-left: 2px;
|
||||
margin-right: auto; }
|
||||
|
||||
.select2-container--default.select2-container--focus .select2-selection--multiple {
|
||||
border: solid black 1px;
|
||||
outline: 0; }
|
||||
|
||||
.select2-container--default.select2-container--disabled .select2-selection--multiple {
|
||||
background-color: #eee;
|
||||
cursor: default; }
|
||||
|
||||
.select2-container--default.select2-container--disabled .select2-selection__choice__remove {
|
||||
display: none; }
|
||||
|
||||
.select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0; }
|
||||
|
||||
.select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0; }
|
||||
|
||||
.select2-container--default .select2-search--dropdown .select2-search__field {
|
||||
border: 1px solid #aaa; }
|
||||
|
||||
.select2-container--default .select2-search--inline .select2-search__field {
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: 0;
|
||||
box-shadow: none;
|
||||
-webkit-appearance: textfield; }
|
||||
|
||||
.select2-container--default .select2-results > .select2-results__options {
|
||||
max-height: 200px;
|
||||
overflow-y: auto; }
|
||||
|
||||
.select2-container--default .select2-results__option[role=group] {
|
||||
padding: 0; }
|
||||
|
||||
.select2-container--default .select2-results__option[aria-disabled=true] {
|
||||
color: #999; }
|
||||
|
||||
.select2-container--default .select2-results__option[aria-selected=true] {
|
||||
background-color: #ddd; }
|
||||
|
||||
.select2-container--default .select2-results__option .select2-results__option {
|
||||
padding-left: 1em; }
|
||||
.select2-container--default .select2-results__option .select2-results__option .select2-results__group {
|
||||
padding-left: 0; }
|
||||
.select2-container--default .select2-results__option .select2-results__option .select2-results__option {
|
||||
margin-left: -1em;
|
||||
padding-left: 2em; }
|
||||
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
||||
margin-left: -2em;
|
||||
padding-left: 3em; }
|
||||
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
||||
margin-left: -3em;
|
||||
padding-left: 4em; }
|
||||
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
||||
margin-left: -4em;
|
||||
padding-left: 5em; }
|
||||
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
||||
margin-left: -5em;
|
||||
padding-left: 6em; }
|
||||
|
||||
.select2-container--default .select2-results__option--highlighted[aria-selected] {
|
||||
background-color: #5897fb;
|
||||
color: white; }
|
||||
|
||||
.select2-container--default .select2-results__group {
|
||||
cursor: default;
|
||||
display: block;
|
||||
padding: 6px; }
|
||||
|
||||
.select2-container--classic .select2-selection--single {
|
||||
background-color: #f7f7f7;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px;
|
||||
outline: 0;
|
||||
background-image: -webkit-linear-gradient(top, white 50%, #eeeeee 100%);
|
||||
background-image: -o-linear-gradient(top, white 50%, #eeeeee 100%);
|
||||
background-image: linear-gradient(to bottom, white 50%, #eeeeee 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
|
||||
.select2-container--classic .select2-selection--single:focus {
|
||||
border: 1px solid #5897fb; }
|
||||
.select2-container--classic .select2-selection--single .select2-selection__rendered {
|
||||
color: #444;
|
||||
line-height: 28px; }
|
||||
.select2-container--classic .select2-selection--single .select2-selection__clear {
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
font-weight: bold;
|
||||
margin-right: 10px; }
|
||||
.select2-container--classic .select2-selection--single .select2-selection__placeholder {
|
||||
color: #999; }
|
||||
.select2-container--classic .select2-selection--single .select2-selection__arrow {
|
||||
background-color: #ddd;
|
||||
border: none;
|
||||
border-left: 1px solid #aaa;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
height: 26px;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
width: 20px;
|
||||
background-image: -webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
|
||||
background-image: -o-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
|
||||
background-image: linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0); }
|
||||
.select2-container--classic .select2-selection--single .select2-selection__arrow b {
|
||||
border-color: #888 transparent transparent transparent;
|
||||
border-style: solid;
|
||||
border-width: 5px 4px 0 4px;
|
||||
height: 0;
|
||||
left: 50%;
|
||||
margin-left: -4px;
|
||||
margin-top: -2px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 0; }
|
||||
|
||||
.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear {
|
||||
float: left; }
|
||||
|
||||
.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow {
|
||||
border: none;
|
||||
border-right: 1px solid #aaa;
|
||||
border-radius: 0;
|
||||
border-top-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
left: 1px;
|
||||
right: auto; }
|
||||
|
||||
.select2-container--classic.select2-container--open .select2-selection--single {
|
||||
border: 1px solid #5897fb; }
|
||||
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow {
|
||||
background: transparent;
|
||||
border: none; }
|
||||
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b {
|
||||
border-color: transparent transparent #888 transparent;
|
||||
border-width: 0 4px 5px 4px; }
|
||||
|
||||
.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single {
|
||||
border-top: none;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
background-image: -webkit-linear-gradient(top, white 0%, #eeeeee 50%);
|
||||
background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%);
|
||||
background-image: linear-gradient(to bottom, white 0%, #eeeeee 50%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
|
||||
|
||||
.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single {
|
||||
border-bottom: none;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
background-image: -webkit-linear-gradient(top, #eeeeee 50%, white 100%);
|
||||
background-image: -o-linear-gradient(top, #eeeeee 50%, white 100%);
|
||||
background-image: linear-gradient(to bottom, #eeeeee 50%, white 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0); }
|
||||
|
||||
.select2-container--classic .select2-selection--multiple {
|
||||
background-color: white;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px;
|
||||
cursor: text;
|
||||
outline: 0; }
|
||||
.select2-container--classic .select2-selection--multiple:focus {
|
||||
border: 1px solid #5897fb; }
|
||||
.select2-container--classic .select2-selection--multiple .select2-selection__rendered {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0 5px; }
|
||||
.select2-container--classic .select2-selection--multiple .select2-selection__clear {
|
||||
display: none; }
|
||||
.select2-container--classic .select2-selection--multiple .select2-selection__choice {
|
||||
background-color: #e4e4e4;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px;
|
||||
cursor: default;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
margin-top: 5px;
|
||||
padding: 0 5px; }
|
||||
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove {
|
||||
color: #888;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
margin-right: 2px; }
|
||||
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover {
|
||||
color: #555; }
|
||||
|
||||
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
|
||||
float: right; }
|
||||
|
||||
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
|
||||
margin-left: 5px;
|
||||
margin-right: auto; }
|
||||
|
||||
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
|
||||
margin-left: 2px;
|
||||
margin-right: auto; }
|
||||
|
||||
.select2-container--classic.select2-container--open .select2-selection--multiple {
|
||||
border: 1px solid #5897fb; }
|
||||
|
||||
.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple {
|
||||
border-top: none;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0; }
|
||||
|
||||
.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple {
|
||||
border-bottom: none;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0; }
|
||||
|
||||
.select2-container--classic .select2-search--dropdown .select2-search__field {
|
||||
border: 1px solid #aaa;
|
||||
outline: 0; }
|
||||
|
||||
.select2-container--classic .select2-search--inline .select2-search__field {
|
||||
outline: 0;
|
||||
box-shadow: none; }
|
||||
|
||||
.select2-container--classic .select2-dropdown {
|
||||
background-color: white;
|
||||
border: 1px solid transparent; }
|
||||
|
||||
.select2-container--classic .select2-dropdown--above {
|
||||
border-bottom: none; }
|
||||
|
||||
.select2-container--classic .select2-dropdown--below {
|
||||
border-top: none; }
|
||||
|
||||
.select2-container--classic .select2-results > .select2-results__options {
|
||||
max-height: 200px;
|
||||
overflow-y: auto; }
|
||||
|
||||
.select2-container--classic .select2-results__option[role=group] {
|
||||
padding: 0; }
|
||||
|
||||
.select2-container--classic .select2-results__option[aria-disabled=true] {
|
||||
color: grey; }
|
||||
|
||||
.select2-container--classic .select2-results__option--highlighted[aria-selected] {
|
||||
background-color: #3875d7;
|
||||
color: white; }
|
||||
|
||||
.select2-container--classic .select2-results__group {
|
||||
cursor: default;
|
||||
display: block;
|
||||
padding: 6px; }
|
||||
|
||||
.select2-container--classic.select2-container--open .select2-dropdown {
|
||||
border-color: #5897fb; }
|
||||
6436
imports/util/select2/select2.full.js
vendored
Normal file
6436
imports/util/select2/select2.full.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
5725
imports/util/select2/select2.js
vendored
Normal file
5725
imports/util/select2/select2.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
314
imports/util/selectize/selectize.default.import.styl
vendored
Normal file
314
imports/util/selectize/selectize.default.import.styl
vendored
Normal file
@@ -0,0 +1,314 @@
|
||||
/**
|
||||
* selectize.default.css (v0.12.0) - Default Theme
|
||||
* Copyright (c) 2013–2015 Brian Reavis & contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
|
||||
* file except in compliance with the License. You may obtain a copy of the License at:
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under
|
||||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
* ANY KIND, either express or implied. See the License for the specific language
|
||||
* governing permissions and limitations under the License.
|
||||
*
|
||||
* @author Brian Reavis <brian@thirdroute.com>
|
||||
*/
|
||||
.selectize-control.plugin-drag_drop.multi > .selectize-input > div.ui-sortable-placeholder
|
||||
visibility visible !important
|
||||
background #f2f2f2 !important
|
||||
background rgba(0, 0, 0, 0.06) !important
|
||||
border 0 none !important
|
||||
-webkit-box-shadow inset 0 0 12px 4px #ffffff
|
||||
box-shadow inset 0 0 12px 4px #ffffff
|
||||
.selectize-control.plugin-drag_drop .ui-sortable-placeholder::after
|
||||
content '!'
|
||||
visibility hidden
|
||||
.selectize-control.plugin-drag_drop .ui-sortable-helper
|
||||
-webkit-box-shadow 0 2px 5px rgba(0, 0, 0, 0.2)
|
||||
box-shadow 0 2px 5px rgba(0, 0, 0, 0.2)
|
||||
.selectize-dropdown-header
|
||||
position relative
|
||||
padding 5px 8px
|
||||
border-bottom 1px solid #d0d0d0
|
||||
background #f8f8f8
|
||||
-webkit-border-radius 3px 3px 0 0
|
||||
-moz-border-radius 3px 3px 0 0
|
||||
border-radius 3px 3px 0 0
|
||||
.selectize-dropdown-header-close
|
||||
position absolute
|
||||
right 8px
|
||||
top 50%
|
||||
color #303030
|
||||
opacity 0.4
|
||||
margin-top -12px
|
||||
line-height 20px
|
||||
font-size 20px !important
|
||||
.selectize-dropdown-header-close:hover
|
||||
color #000000
|
||||
.selectize-dropdown.plugin-optgroup_columns .optgroup
|
||||
border-right 1px solid #f2f2f2
|
||||
border-top 0 none
|
||||
float left
|
||||
-webkit-box-sizing border-box
|
||||
-moz-box-sizing border-box
|
||||
box-sizing border-box
|
||||
.selectize-dropdown.plugin-optgroup_columns .optgroup:last-child
|
||||
border-right 0 none
|
||||
.selectize-dropdown.plugin-optgroup_columns .optgroup:before
|
||||
display none
|
||||
.selectize-dropdown.plugin-optgroup_columns .optgroup-header
|
||||
border-top 0 none
|
||||
.selectize-control.plugin-remove_button [data-value]
|
||||
position relative
|
||||
padding-right 24px !important
|
||||
.selectize-control.plugin-remove_button [data-value] .remove
|
||||
z-index 1
|
||||
/* fixes ie bug (see #392) */
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
bottom 0
|
||||
width 17px
|
||||
text-align center
|
||||
font-weight bold
|
||||
font-size 12px
|
||||
color inherit
|
||||
text-decoration none
|
||||
vertical-align middle
|
||||
display inline-block
|
||||
padding 2px 0 0 0
|
||||
border-left 1px solid #0073bb
|
||||
-webkit-border-radius 0 2px 2px 0
|
||||
-moz-border-radius 0 2px 2px 0
|
||||
border-radius 0 2px 2px 0
|
||||
-webkit-box-sizing border-box
|
||||
-moz-box-sizing border-box
|
||||
box-sizing border-box
|
||||
.selectize-control.plugin-remove_button [data-value] .remove:hover
|
||||
background rgba(0, 0, 0, 0.05)
|
||||
.selectize-control.plugin-remove_button [data-value].active .remove
|
||||
border-left-color #00578d
|
||||
.selectize-control.plugin-remove_button .disabled [data-value] .remove:hover
|
||||
background none
|
||||
.selectize-control.plugin-remove_button .disabled [data-value] .remove
|
||||
border-left-color #aaaaaa
|
||||
.selectize-control
|
||||
position relative
|
||||
.selectize-dropdown, .selectize-input, .selectize-input input
|
||||
color #303030
|
||||
font-family inherit
|
||||
font-size 13px
|
||||
line-height 18px
|
||||
-webkit-font-smoothing inherit
|
||||
.selectize-input, .selectize-control.single .selectize-input.input-active
|
||||
background #ffffff
|
||||
cursor text
|
||||
display inline-block
|
||||
.selectize-input
|
||||
border 1px solid #d0d0d0
|
||||
padding 8px 8px
|
||||
display inline-block
|
||||
width 100%
|
||||
overflow hidden
|
||||
position relative
|
||||
z-index 1
|
||||
-webkit-box-sizing border-box
|
||||
-moz-box-sizing border-box
|
||||
box-sizing border-box
|
||||
-webkit-box-shadow inset 0 1px 1px rgba(0, 0, 0, 0.1)
|
||||
box-shadow inset 0 1px 1px rgba(0, 0, 0, 0.1)
|
||||
-webkit-border-radius 3px
|
||||
-moz-border-radius 3px
|
||||
border-radius 3px
|
||||
.selectize-control.multi .selectize-input.has-items
|
||||
padding 5px 8px 2px
|
||||
.selectize-input.full
|
||||
background-color #ffffff
|
||||
.selectize-input.disabled, .selectize-input.disabled *
|
||||
cursor default !important
|
||||
.selectize-input.focus
|
||||
-webkit-box-shadow inset 0 1px 2px rgba(0, 0, 0, 0.15)
|
||||
box-shadow inset 0 1px 2px rgba(0, 0, 0, 0.15)
|
||||
.selectize-input.dropdown-active
|
||||
-webkit-border-radius 3px 3px 0 0
|
||||
-moz-border-radius 3px 3px 0 0
|
||||
border-radius 3px 3px 0 0
|
||||
.selectize-input > *
|
||||
vertical-align baseline
|
||||
display -moz-inline-stack
|
||||
display inline-block
|
||||
zoom 1
|
||||
*display inline .selectize-control.multi .selectize-input > div
|
||||
cursor pointer
|
||||
margin 0 3px 3px 0
|
||||
padding 2px 6px
|
||||
background #1da7ee
|
||||
color #ffffff
|
||||
border 1px solid #0073bb
|
||||
.selectize-control.multi .selectize-input > div.active
|
||||
background #92c836
|
||||
color #ffffff
|
||||
border 1px solid #00578d
|
||||
.selectize-control.multi .selectize-input.disabled > div, .selectize-control.multi .selectize-input.disabled > div.active
|
||||
color #ffffff
|
||||
background #d2d2d2
|
||||
border 1px solid #aaaaaa
|
||||
.selectize-input > input
|
||||
display inline-block !important
|
||||
padding 0 !important
|
||||
min-height 0 !important
|
||||
max-height none !important
|
||||
max-width 100% !important
|
||||
margin 0 1px !important
|
||||
text-indent 0 !important
|
||||
border 0 none !important
|
||||
background none !important
|
||||
line-height inherit !important
|
||||
-webkit-user-select auto !important
|
||||
-webkit-box-shadow none !important
|
||||
box-shadow none !important
|
||||
.selectize-input > input::-ms-clear
|
||||
display none
|
||||
.selectize-input > input:focus
|
||||
outline none !important
|
||||
.selectize-input::after
|
||||
content ' '
|
||||
display block
|
||||
clear left
|
||||
.selectize-input.dropdown-active::before
|
||||
content ' '
|
||||
display block
|
||||
position absolute
|
||||
background #f0f0f0
|
||||
height 1px
|
||||
bottom 0
|
||||
left 0
|
||||
right 0
|
||||
.selectize-dropdown
|
||||
position absolute
|
||||
z-index 10
|
||||
border 1px solid #d0d0d0
|
||||
background #ffffff
|
||||
margin -1px 0 0 0
|
||||
border-top 0 none
|
||||
-webkit-box-sizing border-box
|
||||
-moz-box-sizing border-box
|
||||
box-sizing border-box
|
||||
-webkit-box-shadow 0 1px 3px rgba(0, 0, 0, 0.1)
|
||||
box-shadow 0 1px 3px rgba(0, 0, 0, 0.1)
|
||||
-webkit-border-radius 0 0 3px 3px
|
||||
-moz-border-radius 0 0 3px 3px
|
||||
border-radius 0 0 3px 3px
|
||||
.selectize-dropdown [data-selectable]
|
||||
cursor pointer
|
||||
overflow hidden
|
||||
.selectize-dropdown [data-selectable] .highlight
|
||||
background rgba(125, 168, 208, 0.2)
|
||||
-webkit-border-radius 1px
|
||||
-moz-border-radius 1px
|
||||
border-radius 1px
|
||||
.selectize-dropdown [data-selectable], .selectize-dropdown .optgroup-header
|
||||
padding 5px 8px
|
||||
.selectize-dropdown .optgroup:first-child .optgroup-header
|
||||
border-top 0 none
|
||||
.selectize-dropdown .optgroup-header
|
||||
color #303030
|
||||
background #ffffff
|
||||
cursor default
|
||||
.selectize-dropdown .active
|
||||
background-color #f5fafd
|
||||
color #495c68
|
||||
.selectize-dropdown .active.create
|
||||
color #495c68
|
||||
.selectize-dropdown .create
|
||||
color rgba(48, 48, 48, 0.5)
|
||||
.selectize-dropdown-content
|
||||
overflow-y auto
|
||||
overflow-x hidden
|
||||
max-height 200px
|
||||
.selectize-control.single .selectize-input, .selectize-control.single .selectize-input input
|
||||
cursor pointer
|
||||
.selectize-control.single .selectize-input.input-active, .selectize-control.single .selectize-input.input-active input
|
||||
cursor text
|
||||
.selectize-control.single .selectize-input:after
|
||||
content ' '
|
||||
display block
|
||||
position absolute
|
||||
top 50%
|
||||
right 15px
|
||||
margin-top -3px
|
||||
width 0
|
||||
height 0
|
||||
border-style solid
|
||||
border-width 5px 5px 0 5px
|
||||
border-color #808080 transparent transparent transparent
|
||||
.selectize-control.single .selectize-input.dropdown-active:after
|
||||
margin-top -4px
|
||||
border-width 0 5px 5px 5px
|
||||
border-color transparent transparent #808080 transparent
|
||||
.selectize-control.rtl.single .selectize-input:after
|
||||
left 15px
|
||||
right auto
|
||||
.selectize-control.rtl .selectize-input > input
|
||||
margin 0 4px 0 -2px !important
|
||||
.selectize-control .selectize-input.disabled
|
||||
opacity 0.5
|
||||
background-color #fafafa
|
||||
.selectize-control.multi .selectize-input.has-items
|
||||
padding-left 5px
|
||||
padding-right 5px
|
||||
.selectize-control.multi .selectize-input.disabled [data-value]
|
||||
color #999
|
||||
text-shadow none
|
||||
background none
|
||||
-webkit-box-shadow none
|
||||
box-shadow none
|
||||
.selectize-control.multi .selectize-input.disabled [data-value], .selectize-control.multi .selectize-input.disabled [data-value] .remove
|
||||
border-color #e6e6e6
|
||||
.selectize-control.multi .selectize-input.disabled [data-value] .remove
|
||||
background none
|
||||
.selectize-control.multi .selectize-input [data-value]
|
||||
text-shadow 0 1px 0 rgba(0, 51, 83, 0.3)
|
||||
-webkit-border-radius 3px
|
||||
-moz-border-radius 3px
|
||||
border-radius 3px
|
||||
background-color #1b9dec
|
||||
background-image -moz-linear-gradient(top, rgb(29, 167, 238, 1), rgb(23, 142, 233, 1))
|
||||
background-image -webkit-gradient(linear, 0 0, 0 100%, from(rgb(29, 167, 238, 1)), to(rgb(23, 142, 233, 1)))
|
||||
background-image -webkit-linear-gradient(top, rgb(29, 167, 238, 1), rgb(23, 142, 233, 1))
|
||||
background-image -o-linear-gradient(top, rgb(29, 167, 238, 1), rgb(23, 142, 233, 1))
|
||||
background-image linear-gradient(to bottom, rgb(29, 167, 238, 1), rgb(23, 142, 233, 1))
|
||||
background-repeat repeat-x
|
||||
filter unquote("progid:DXImageTransform.Microsoft.gradient('#ff1da7ee', '#ff178ee9', 0)")
|
||||
-webkit-box-shadow 0 1px 0 rgba(0, 0, 0, 0.2), inset 0 1px rgba(255, 255, 255, 0.03)
|
||||
box-shadow 0 1px 0 rgba(0, 0, 0, 0.2), inset 0 1px rgba(255, 255, 255, 0.03)
|
||||
.selectize-control.multi .selectize-input [data-value].active
|
||||
background-color #0085d4
|
||||
background-image -moz-linear-gradient(top, rgb(0, 143, 216, 1), rgb(0, 117, 207, 1))
|
||||
background-image -webkit-gradient(linear, 0 0, 0 100%, from(rgb(0, 143, 216, 1)), to(rgb(0, 117, 207, 1)))
|
||||
background-image -webkit-linear-gradient(top, rgb(0, 143, 216, 1), rgb(0, 117, 207, 1))
|
||||
background-image -o-linear-gradient(top, rgb(0, 143, 216, 1), rgb(0, 117, 207, 1))
|
||||
background-image linear-gradient(to bottom, rgb(0, 143, 216, 1), rgb(0, 117, 207, 1))
|
||||
background-repeat repeat-x
|
||||
filter unquote("progid:DXImageTransform.Microsoft.gradient('#ff008fd8', '#ff0075cf', 0)")
|
||||
.selectize-control.single .selectize-input
|
||||
-webkit-box-shadow 0 1px 0 rgba(0, 0, 0, 0.05), inset 0 1px 0 rgba(255, 255, 255, 0.8)
|
||||
box-shadow 0 1px 0 rgba(0, 0, 0, 0.05), inset 0 1px 0 rgba(255, 255, 255, 0.8)
|
||||
background-color #f9f9f9
|
||||
background-image -moz-linear-gradient(top, rgb(254, 254, 254, 1), rgb(242, 242, 242, 1))
|
||||
background-image -webkit-gradient(linear, 0 0, 0 100%, from(rgb(254, 254, 254, 1)), to(rgb(242, 242, 242, 1)))
|
||||
background-image -webkit-linear-gradient(top, rgb(254, 254, 254, 1), rgb(242, 242, 242, 1))
|
||||
background-image -o-linear-gradient(top, rgb(254, 254, 254, 1), rgb(242, 242, 242, 1))
|
||||
background-image linear-gradient(to bottom, rgb(254, 254, 254, 1), rgb(242, 242, 242, 1))
|
||||
background-repeat repeat-x
|
||||
filter unquote("progid:DXImageTransform.Microsoft.gradient('#fffefefe', '#fff2f2f2', 0)")
|
||||
.selectize-control.single .selectize-input, .selectize-dropdown.single
|
||||
border-color #b8b8b8
|
||||
.selectize-dropdown .optgroup-header
|
||||
padding-top 7px
|
||||
font-weight bold
|
||||
font-size 0.85em
|
||||
.selectize-dropdown .optgroup
|
||||
border-top 1px solid #f0f0f0
|
||||
.selectize-dropdown .optgroup:first-child
|
||||
border-top 0 none
|
||||
57
imports/util/selectize/selectize.html
Normal file
57
imports/util/selectize/selectize.html
Normal file
@@ -0,0 +1,57 @@
|
||||
<template name="Selectize">
|
||||
<div class="selectize-control selectize {{#if multiple}}multi{{else}}single{{/if}} {{#if removeButton}}plugin-remove_button{{/if}} {{#if loading}}loading{{/if}}">
|
||||
<select name={{name}} multiple={{multiple}} id={{id}} data-schema-key={{dataSchemaKey}}>
|
||||
<option value=""></option>
|
||||
{{#each getItems}}
|
||||
<option value={{value}} selected={{selected}}>{{label}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
|
||||
<div class="selectize-input items not-full has-options has-items {{#if disabled}}disabled{{/if}} {{#if open}}focus input-active dropdown-active{{/if}}">
|
||||
{{#if getPlaceholder}}
|
||||
<div class="selectize-placeholder {{#if open}}hidden{{/if}}">
|
||||
{{getPlaceholder}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if inputPosition -1}}
|
||||
<input type="text" autocomplete="off" tabindex="" class="js-selectizeInput">
|
||||
{{/if}}
|
||||
|
||||
{{#each getItemsSelected}}
|
||||
<div data-value={{value}} class="item">
|
||||
{{label}}
|
||||
{{#if removeButton}}
|
||||
<a href="#" class="remove" tabindex="-1" title="Remove">×</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if inputPosition @index}}
|
||||
<input type="text" autocomplete="off" tabindex="" class="js-selectizeInput">
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
<div class="selectize-dropdown js-selectize-dropdown {{#unless open}}hidden{{/unless}}">
|
||||
<div class="selectize-dropdown-content">
|
||||
{{#each getItemsUnselected}}
|
||||
<div data-value={{value}} data-index={{@index}} data-selectable class="option {{#if activeOption @index}}active{{/if}} {{isPlaceholder}}">
|
||||
{{label}}
|
||||
</div>
|
||||
{{/each}}
|
||||
{{#if create}}
|
||||
{{#if getSearchText}}
|
||||
<div data-selectable="" data-index='create' class="create {{#if activeOption 'create'}}active{{/if}}">
|
||||
{{#if createText}}
|
||||
{{{createText}}}
|
||||
{{else}}
|
||||
Add
|
||||
{{/if}}
|
||||
<strong>{{getSearchText}}</strong>…
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
38
imports/util/selectize/selectize.import.styl
vendored
Normal file
38
imports/util/selectize/selectize.import.styl
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
|
||||
.selectize.selectize-control
|
||||
position relative
|
||||
.hidden
|
||||
display none
|
||||
select
|
||||
display none
|
||||
.selectize-input
|
||||
input
|
||||
width 4px
|
||||
opacity 1
|
||||
position relative
|
||||
left 0px
|
||||
div.selectize-placeholder
|
||||
color #bbb
|
||||
background none
|
||||
border none
|
||||
&.input-active
|
||||
div.selectize-placeholder
|
||||
display none
|
||||
.selectize-dropdown
|
||||
width 100% !important
|
||||
top 36px
|
||||
&.loading:after
|
||||
content ""
|
||||
width 16px
|
||||
height 16px
|
||||
//background '/packages/vazco_selectize/img/loading.gif' no-repeat
|
||||
background 'public/images/loading.gif' no-repeat
|
||||
position absolute
|
||||
top 10px
|
||||
right 10px
|
||||
z-index 1000
|
||||
&.multi
|
||||
.selectize-input
|
||||
height 36px
|
||||
.uniPlaceholder
|
||||
color #bbb
|
||||
696
imports/util/selectize/selectize.js
Normal file
696
imports/util/selectize/selectize.js
Normal file
@@ -0,0 +1,696 @@
|
||||
|
||||
import './selectize.html';
|
||||
|
||||
/* Meteor need globals */
|
||||
/* eslint strict: 0 */
|
||||
/* jshint strict: false */
|
||||
|
||||
UniSelectize = function (options, template) {
|
||||
this.items = new ReactiveVar([]);
|
||||
this.itemsSelected = new ReactiveVar([]);
|
||||
this.itemsUnselected = new ReactiveVar([]);
|
||||
|
||||
this.open = new ReactiveVar(false);
|
||||
this.loading = new ReactiveVar(false);
|
||||
this.searchText = new ReactiveVar();
|
||||
this.activeOption = new ReactiveVar(-1);
|
||||
this.inputPosition = new ReactiveVar(-1);
|
||||
this.optionsMethodParams = new ReactiveVar();
|
||||
|
||||
this.create = options.create;
|
||||
this.template = template;
|
||||
this.multiple = options.multiple;
|
||||
this.sortMethod = _.isUndefined(options.sortMethod) ? 'label' : options.sortMethod;
|
||||
this.placeholder = options.placeholder;
|
||||
this.removeButton = options.removeButton !== false;
|
||||
this.createMethod = options.createMethod;
|
||||
this.optionsMethod = options.optionsMethod;
|
||||
this.optionsPlaceholder = options.optionsPlaceholder;
|
||||
|
||||
};
|
||||
|
||||
UniSelectize.prototype.triggerChangeEvent = function() {
|
||||
var self = this;
|
||||
Meteor.defer(function () {
|
||||
$(self.template.find('select')).change();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
UniSelectize.prototype.setItems = function (items, value) {
|
||||
if (!_.isArray(items)) {
|
||||
console.warn('invalid options format');
|
||||
}
|
||||
|
||||
var values = value && (_.isArray(value) ? value : [value]);
|
||||
|
||||
items = _.filter(items, function (item) {
|
||||
if (!item.value || !item.label) {
|
||||
console.info('invalid option', item);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
var itemValues = items.map(function (item) {
|
||||
return item.value;
|
||||
});
|
||||
|
||||
_.each(values, function (val) {
|
||||
if (!_.contains(itemValues, val) && val) {
|
||||
items.push({
|
||||
value: val,
|
||||
label: val
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_.each(items, function (item) {
|
||||
if (_.contains(values, item.value)) {
|
||||
item.selected = true;
|
||||
}
|
||||
});
|
||||
this.items.set(items);
|
||||
};
|
||||
|
||||
UniSelectize.prototype.addItems = function (newItems, value) {
|
||||
if (!_.isArray(newItems)) {
|
||||
console.warn('invalid options format');
|
||||
}
|
||||
|
||||
var values = value && (_.isArray(value) ? value : [value]);
|
||||
var items = this.items.get();
|
||||
var itemsValues = items.map(function (item) {
|
||||
return item.value;
|
||||
});
|
||||
|
||||
_.each(newItems, function (newItem) {
|
||||
if (!newItem.value || !newItem.label) {
|
||||
console.info('invalid option', newItem);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_.contains(itemsValues, newItem.value)) {
|
||||
var item = {
|
||||
value: newItem.value,
|
||||
label: newItem.label,
|
||||
selected: newItem.selected
|
||||
};
|
||||
|
||||
if (_.contains(values, newItem.value)) {
|
||||
item.selected = true;
|
||||
}
|
||||
|
||||
items.push(item);
|
||||
} else if (typeof newItem.selected !== 'undefined') {
|
||||
var item = _.find(items, function (item) {
|
||||
return item.value === newItem.value;
|
||||
});
|
||||
item.selected = newItem.selected;
|
||||
}
|
||||
});
|
||||
|
||||
this.items.set(items);
|
||||
};
|
||||
|
||||
|
||||
UniSelectize.prototype.removeUnusedItems = function (newItems) {
|
||||
if (!_.isArray(newItems)) {
|
||||
console.warn('invalid options format');
|
||||
}
|
||||
|
||||
var items = this.items.get();
|
||||
var newItemsValues = newItems.map(function (item) {
|
||||
return item.value;
|
||||
});
|
||||
|
||||
items = _.filter(items, function (item) {
|
||||
return _.contains(newItemsValues, item.value) || item.selected;
|
||||
});
|
||||
|
||||
this.items.set(items);
|
||||
};
|
||||
|
||||
UniSelectize.prototype.itemsAutorun = function () {
|
||||
var items = this.items.get();
|
||||
var itemsSelected = [];
|
||||
var itemsUnselected = [];
|
||||
|
||||
_.each(items, function (item) {
|
||||
if (item.selected) {
|
||||
itemsSelected.push(item);
|
||||
} else {
|
||||
itemsUnselected.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
if (this.sortMethod) {
|
||||
itemsSelected = _.sortBy(itemsSelected, this.sortMethod);
|
||||
itemsUnselected = _.sortBy(itemsUnselected, this.sortMethod);
|
||||
}
|
||||
|
||||
var itemsSelectedPrev = this.itemsSelected.get();
|
||||
if (!_.isEqual(itemsSelectedPrev, itemsSelected)) {
|
||||
this.itemsSelected.set(itemsSelected);
|
||||
}
|
||||
|
||||
if (this.placeholder && this.optionsPlaceholder) {
|
||||
itemsUnselected.unshift({
|
||||
value: '',
|
||||
label: _.isString(this.optionsPlaceholder) ? this.optionsPlaceholder: this.placeholder
|
||||
});
|
||||
}
|
||||
|
||||
this.itemsUnselected.set(itemsUnselected);
|
||||
};
|
||||
|
||||
UniSelectize.prototype.itemsSelectedAutorun = function () {
|
||||
var itemsSelected = this.template.uniSelectize.itemsSelected.get();
|
||||
this.template.uniSelectize.inputPosition.set(itemsSelected.length - 1);
|
||||
};
|
||||
|
||||
UniSelectize.prototype.inputFocus = function () {
|
||||
var self = this;
|
||||
Meteor.defer(function () {
|
||||
var $input = $(self.template.find('input'));
|
||||
$input.focus();
|
||||
});
|
||||
};
|
||||
|
||||
UniSelectize.prototype.selectItem = function (value) {
|
||||
var items = this.items.get();
|
||||
var multiple = this.multiple;
|
||||
|
||||
_.each(items, function (item) {
|
||||
if (value === '') {
|
||||
item.selected = false;
|
||||
} else if (item.value === value) {
|
||||
item.selected = true;
|
||||
} else if (!multiple) {
|
||||
item.selected = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.setItems(items);
|
||||
this.triggerChangeEvent();
|
||||
};
|
||||
|
||||
UniSelectize.prototype.unselectItem = function (value, reset) {
|
||||
var items = this.items.get();
|
||||
|
||||
_.each(items, function (item) {
|
||||
if (item.value === value || reset) {
|
||||
item.selected = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.setItems(items);
|
||||
this.triggerChangeEvent()
|
||||
};
|
||||
|
||||
UniSelectize.prototype.removeItemBeforeInput = function () {
|
||||
var items = this.itemsSelected.get();
|
||||
var inputPosition = this.inputPosition.get();
|
||||
var itemToRemove;
|
||||
|
||||
_.each(items, function (item, index) {
|
||||
if (index === inputPosition) {
|
||||
itemToRemove = item;
|
||||
}
|
||||
});
|
||||
|
||||
if (itemToRemove) {
|
||||
this.unselectItem(itemToRemove.value, false);
|
||||
}
|
||||
};
|
||||
|
||||
UniSelectize.prototype.removeItemAfterInput = function () {
|
||||
var items = this.itemsSelected.get();
|
||||
var inputPosition = this.inputPosition.get();
|
||||
var itemToRemove;
|
||||
|
||||
_.each(items, function (item, index) {
|
||||
if (index === inputPosition + 1) {
|
||||
itemToRemove = item;
|
||||
}
|
||||
});
|
||||
|
||||
if (itemToRemove) {
|
||||
this.unselectItem(itemToRemove.value, false);
|
||||
}
|
||||
};
|
||||
|
||||
UniSelectize.prototype.selectActiveItem = function () {
|
||||
var itemsUnselected = this.getItemsUnselectedFiltered();
|
||||
var activeOption = this.activeOption.get();
|
||||
var itemToSelect = itemsUnselected && itemsUnselected[activeOption];
|
||||
|
||||
if (activeOption === itemsUnselected.length && this.create) {
|
||||
this.createItem();
|
||||
return;
|
||||
}
|
||||
|
||||
itemToSelect && this.selectItem(itemToSelect.value);
|
||||
|
||||
if (this.multiple) {
|
||||
this.open.set(true);
|
||||
this.inputFocus();
|
||||
} else {
|
||||
this.open.set(false);
|
||||
}
|
||||
};
|
||||
|
||||
UniSelectize.prototype.insertItem = function (item) {
|
||||
var items = this.items.get();
|
||||
|
||||
if (!_.find(items, function (obj) {
|
||||
if (obj.value === item.value) {
|
||||
obj.selected = item.selected;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})) {
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
this.setItems(items);
|
||||
this.triggerChangeEvent();
|
||||
};
|
||||
|
||||
UniSelectize.prototype.createItem = function () {
|
||||
var self = this;
|
||||
var template = this.template;
|
||||
var searchText = this.searchText.get();
|
||||
|
||||
if (!searchText) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var item = {
|
||||
label: searchText,
|
||||
value: searchText,
|
||||
selected: true
|
||||
};
|
||||
|
||||
if (template.uniSelectize.createMethod) {
|
||||
Meteor.call(template.uniSelectize.createMethod, searchText, searchText, function (error, value) {
|
||||
if (error) {
|
||||
console.error('universe selectize create method error:', error);
|
||||
return;
|
||||
}
|
||||
|
||||
Meteor.defer(function () {
|
||||
item.value = value || item.value;
|
||||
self.insertItem(item);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.insertItem(item);
|
||||
}
|
||||
|
||||
if (this.multiple) {
|
||||
this.inputFocus();
|
||||
} else {
|
||||
this.open.set(false);
|
||||
}
|
||||
};
|
||||
|
||||
UniSelectize.prototype.getItemsUnselectedFiltered = function () {
|
||||
var items = this.itemsUnselected.get();
|
||||
var searchText = this.searchText.get();
|
||||
|
||||
return _.filter(items, function (item) {
|
||||
if (item.label && item.label.search(new RegExp(searchText, 'i')) !== -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
UniSelectize.prototype.checkDisabled = function () {
|
||||
if (this.template.data.disabled) {
|
||||
throw new Meteor.Error('This field is disabled');
|
||||
}
|
||||
};
|
||||
|
||||
UniSelectize.prototype.measureString = function (str, $parent) {
|
||||
if (!str) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var $test = $('<test>').css({
|
||||
position: 'absolute',
|
||||
top: -99999,
|
||||
left: -99999,
|
||||
width: 'auto',
|
||||
padding: 0,
|
||||
whiteSpace: 'pre'
|
||||
}).text(str).appendTo('body');
|
||||
|
||||
this.transferStyles($parent, $test, [
|
||||
'letterSpacing',
|
||||
'fontSize',
|
||||
'fontFamily',
|
||||
'fontWeight',
|
||||
'textTransform'
|
||||
]);
|
||||
|
||||
var width = $test.width();
|
||||
$test.remove();
|
||||
|
||||
return width;
|
||||
};
|
||||
|
||||
UniSelectize.prototype.transferStyles = function ($from, $to, properties) {
|
||||
var i, n, styles = {};
|
||||
|
||||
if (properties) {
|
||||
for (i = 0, n = properties.length; i < n; i++) {
|
||||
styles[properties[i]] = $from.css(properties[i]);
|
||||
}
|
||||
} else {
|
||||
styles = $from.css();
|
||||
}
|
||||
|
||||
$to.css(styles);
|
||||
};
|
||||
|
||||
UniSelectize.prototype.getOptionsFromMethod = function (values) {
|
||||
var self = this;
|
||||
var methodName = this.optionsMethod;
|
||||
var searchText = this.searchText.get();
|
||||
var params = this.optionsMethodParams.get();
|
||||
|
||||
if (!methodName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var searchVal = {
|
||||
searchText: searchText,
|
||||
values: values || [],
|
||||
params: params || null
|
||||
};
|
||||
|
||||
self.loading.set(true);
|
||||
Meteor.call(methodName, searchVal, function (err, options) {
|
||||
self.loading.set(false);
|
||||
if (params) {
|
||||
self.removeUnusedItems(options);
|
||||
}
|
||||
self.addItems(options, values);
|
||||
});
|
||||
};
|
||||
|
||||
Template.Selectize.onCreated(function () {
|
||||
var template = this;
|
||||
template.uniSelectize = new UniSelectize(template.data, template);
|
||||
});
|
||||
|
||||
Template.Selectize.onRendered(function () {
|
||||
var template = this;
|
||||
|
||||
template.autorun(function () {
|
||||
var data = Template.currentData();
|
||||
var value = data.value;
|
||||
|
||||
if (template.uniSelectize.optionsMethod) {
|
||||
template.uniSelectize.getOptionsFromMethod(value);
|
||||
} else {
|
||||
var options = data.options;
|
||||
template.uniSelectize.setItems(options, value);
|
||||
}
|
||||
});
|
||||
|
||||
template.autorun(function () {
|
||||
template.uniSelectize.itemsAutorun();
|
||||
});
|
||||
|
||||
template.autorun(function () {
|
||||
template.uniSelectize.itemsSelectedAutorun();
|
||||
});
|
||||
|
||||
template.autorun(function () {
|
||||
var data = Template.currentData();
|
||||
var methodParams = data.optionsMethodParams;
|
||||
var params = _.isFunction(methodParams) ? methodParams() : methodParams;
|
||||
|
||||
template.uniSelectize.optionsMethodParams.set(params);
|
||||
});
|
||||
|
||||
this.form = $(template.find('select')).parents('form');
|
||||
this.form.bind('reset', function () {
|
||||
template.uniSelectize.unselectItem(null, true);
|
||||
});
|
||||
});
|
||||
|
||||
Template.Selectize.onDestroyed(function () {
|
||||
if (this.form) {
|
||||
this.form.unbind('reset');
|
||||
}
|
||||
});
|
||||
|
||||
Template.Selectize.helpers({
|
||||
multiple: function () {
|
||||
var template = Template.instance();
|
||||
return template.uniSelectize.multiple;
|
||||
},
|
||||
removeButton: function () {
|
||||
var template = Template.instance();
|
||||
return template.uniSelectize.multiple && template.uniSelectize.removeButton;
|
||||
},
|
||||
getItems: function () {
|
||||
var template = Template.instance();
|
||||
return template.uniSelectize.items.get();
|
||||
},
|
||||
getItemsSelected: function () {
|
||||
var template = Template.instance();
|
||||
return template.uniSelectize.itemsSelected.get();
|
||||
},
|
||||
getItemsUnselected: function () {
|
||||
var template = Template.instance();
|
||||
return template.uniSelectize.getItemsUnselectedFiltered();
|
||||
},
|
||||
getSearchText: function () {
|
||||
var template = Template.instance();
|
||||
return template.uniSelectize.searchText.get();
|
||||
},
|
||||
open: function () {
|
||||
var template = Template.instance();
|
||||
return template.uniSelectize.open.get();
|
||||
},
|
||||
loading: function () {
|
||||
var template = Template.instance();
|
||||
return template.uniSelectize.loading.get();
|
||||
},
|
||||
inputPosition: function (position) {
|
||||
var template = Template.instance();
|
||||
var inputPosition = template.uniSelectize.inputPosition.get();
|
||||
return position === inputPosition;
|
||||
},
|
||||
activeOption: function (position) {
|
||||
var template = Template.instance();
|
||||
var activeOption = template.uniSelectize.activeOption.get();
|
||||
var itemsUnselected = template.uniSelectize.getItemsUnselectedFiltered();
|
||||
var createOption = template.uniSelectize.create;
|
||||
|
||||
if (activeOption === itemsUnselected.length && createOption) {
|
||||
return position === 'create';
|
||||
}
|
||||
|
||||
return position === activeOption;
|
||||
},
|
||||
getPlaceholder: function () {
|
||||
var template = Template.instance();
|
||||
var itemsSelected = template.uniSelectize.itemsSelected.get();
|
||||
|
||||
if (itemsSelected.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return template.uniSelectize.placeholder;
|
||||
},
|
||||
isPlaceholder: function () {
|
||||
return this.value === '' ? 'uniPlaceholder' : '';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Template.Selectize.events({
|
||||
'click .selectize-input': function (e, template) {
|
||||
template.uniSelectize.checkDisabled();
|
||||
template.uniSelectize.inputFocus(template);
|
||||
|
||||
template.uniSelectize.getOptionsFromMethod();
|
||||
},
|
||||
'keydown input.js-selectizeInput': function (e, template) {
|
||||
var uniSelectize = template.uniSelectize;
|
||||
var itemsSelected = uniSelectize.itemsSelected.get();
|
||||
var itemsUnselected = uniSelectize.getItemsUnselectedFiltered();
|
||||
var inputPosition = uniSelectize.inputPosition.get();
|
||||
var activeOption = uniSelectize.activeOption.get();
|
||||
|
||||
template.uniSelectize.checkDisabled();
|
||||
|
||||
var $input = $(e.target);
|
||||
var width = template.uniSelectize.measureString($input.val(), $input) + 10;
|
||||
|
||||
$input.width(width);
|
||||
|
||||
switch (e.keyCode) {
|
||||
case 8: // backspace
|
||||
if ($input.val() === '') {
|
||||
e.preventDefault();
|
||||
uniSelectize.removeItemBeforeInput();
|
||||
}
|
||||
uniSelectize.open.set(true);
|
||||
uniSelectize.inputFocus();
|
||||
|
||||
break;
|
||||
|
||||
case 46: // delete
|
||||
if ($input.val() === '') {
|
||||
uniSelectize.removeItemAfterInput();
|
||||
}
|
||||
uniSelectize.open.set(true);
|
||||
uniSelectize.inputFocus();
|
||||
|
||||
break;
|
||||
|
||||
case 27: // escape
|
||||
$input.blur();
|
||||
break;
|
||||
|
||||
case 13: // enter
|
||||
e.preventDefault();
|
||||
|
||||
if (activeOption === -1 && $input.val() === '') {
|
||||
break;
|
||||
}
|
||||
|
||||
if (itemsUnselected && itemsUnselected.length > 0) {
|
||||
uniSelectize.selectActiveItem(template);
|
||||
uniSelectize.searchText.set('');
|
||||
$input.val('');
|
||||
} else if (uniSelectize.create /*&& createOnBlur*/) {
|
||||
uniSelectize.createItem();
|
||||
uniSelectize.searchText.set('');
|
||||
$input.val('');
|
||||
}
|
||||
|
||||
break;
|
||||
case 37: // left
|
||||
if (!uniSelectize.multiple) {
|
||||
break;
|
||||
}
|
||||
if (inputPosition > -1) {
|
||||
uniSelectize.inputPosition.set(inputPosition - 1);
|
||||
uniSelectize.inputFocus();
|
||||
}
|
||||
break;
|
||||
case 39: // right
|
||||
if (!uniSelectize.multiple) {
|
||||
break;
|
||||
}
|
||||
if (inputPosition < itemsSelected.length - 1) {
|
||||
uniSelectize.inputPosition.set(inputPosition + 1);
|
||||
uniSelectize.inputFocus();
|
||||
}
|
||||
break;
|
||||
case 38: // up
|
||||
if (activeOption > -1) {
|
||||
uniSelectize.activeOption.set(activeOption - 1);
|
||||
}
|
||||
break;
|
||||
case 40: // down
|
||||
if (activeOption < itemsUnselected.length - 1 ||
|
||||
(activeOption < itemsUnselected.length && uniSelectize.create)) {
|
||||
uniSelectize.activeOption.set(activeOption + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!template.uniSelectize.multiple && itemsSelected.length) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'keyup input.js-selectizeInput': function (e, template) {
|
||||
template.uniSelectize.checkDisabled();
|
||||
|
||||
var $el = $(e.target);
|
||||
var value = $el.val();
|
||||
template.uniSelectize.searchText.set(value);
|
||||
template.uniSelectize.getOptionsFromMethod();
|
||||
},
|
||||
'focus input.js-selectizeInput': function (e, template) {
|
||||
template.uniSelectize.checkDisabled();
|
||||
template.uniSelectize.open.set(true);
|
||||
|
||||
Meteor.clearTimeout(template.uniSelectize.timeoutId);
|
||||
},
|
||||
'change input.js-selectizeInput': function(e, template) {
|
||||
template.uniSelectize.checkDisabled();
|
||||
|
||||
// prevent non-autoform fields changes from submitting the form when autosave is enabled
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
},
|
||||
'blur input.js-selectizeInput': function (e, template) {
|
||||
template.uniSelectize.checkDisabled();
|
||||
|
||||
template.uniSelectize.timeoutId = Meteor.setTimeout(function () {
|
||||
template.uniSelectize.open.set(false);
|
||||
}, 500);
|
||||
},
|
||||
'scroll .selectize-dropdown-content': function (e, template) {
|
||||
Meteor.clearTimeout(template.uniSelectize.timeoutId);
|
||||
template.uniSelectize.timeoutId = Meteor.setTimeout(function () {
|
||||
template.uniSelectize.open.set(false);
|
||||
}, 5000);
|
||||
},
|
||||
'click .selectize-dropdown-content > div:not(.create)': function (e, template) {
|
||||
e.preventDefault();
|
||||
template.uniSelectize.checkDisabled();
|
||||
var $input = $(template.find('input'));
|
||||
var itemsUnselected = template.uniSelectize.getItemsUnselectedFiltered();
|
||||
var itemsUnselectedLength = itemsUnselected && itemsUnselected.length;
|
||||
|
||||
template.uniSelectize.selectItem(this.value);
|
||||
template.uniSelectize.searchText.set('');
|
||||
$input.val('');
|
||||
|
||||
if (template.uniSelectize.multiple && itemsUnselectedLength && this.value) {
|
||||
template.uniSelectize.inputFocus();
|
||||
} else {
|
||||
template.uniSelectize.open.set(false);
|
||||
}
|
||||
},
|
||||
'mouseenter .selectize-dropdown-content > div': function (e, template) {
|
||||
var $el = $(e.target);
|
||||
var elIndex = $el.attr('data-index');
|
||||
var itemsUnselected = template.uniSelectize.getItemsUnselectedFiltered();
|
||||
|
||||
if (elIndex === 'create') {
|
||||
elIndex = itemsUnselected.length;
|
||||
} else {
|
||||
elIndex = parseInt(elIndex);
|
||||
}
|
||||
|
||||
template.uniSelectize.activeOption.set(elIndex);
|
||||
},
|
||||
'click .create': function (e, template) {
|
||||
e.preventDefault();
|
||||
template.uniSelectize.checkDisabled();
|
||||
var $input = $(template.find('input'));
|
||||
|
||||
template.uniSelectize.createItem();
|
||||
template.uniSelectize.searchText.set('');
|
||||
$input.val('');
|
||||
},
|
||||
'click .remove': function (e, template) {
|
||||
e.preventDefault();
|
||||
template.uniSelectize.checkDisabled();
|
||||
|
||||
template.uniSelectize.unselectItem(this.value, false);
|
||||
}
|
||||
});
|
||||
0
imports/util/selectize/temp.import.styl
vendored
Normal file
0
imports/util/selectize/temp.import.styl
vendored
Normal file
288
imports/util/simplegrid.css
Normal file
288
imports/util/simplegrid.css
Normal file
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
Simple Grid
|
||||
Project Page - http://thisisdallas.github.com/Simple-Grid/
|
||||
Author - Dallas Bass
|
||||
Site - http://dallasbass.com
|
||||
*/
|
||||
|
||||
|
||||
[class*='grid'],
|
||||
[class*='col-'],
|
||||
[class*='mobile-'],
|
||||
.grid:after {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
[class*='col-'] {
|
||||
float: left;
|
||||
min-height: 1px;
|
||||
padding-right: 20px; /* column-space */
|
||||
}
|
||||
|
||||
[class*='col-'] [class*='col-']:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.grid {
|
||||
width: 100%;
|
||||
max-width: 1140px;
|
||||
min-width: 748px; /* when using padded grid on ipad in portrait mode, width should be viewport-width - padding = (768 - 20) = 748. actually, it should be even smaller to allow for padding of grid containing element */
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.grid:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.grid-pad {
|
||||
padding-top: 20px;
|
||||
padding-left: 20px; /* grid-space to left */
|
||||
padding-right: 0; /* grid-space to right: (grid-space-left - column-space) e.g. 20px-20px=0 */
|
||||
}
|
||||
|
||||
.push-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* Content Columns */
|
||||
|
||||
.col-1-1 {
|
||||
width: 100%;
|
||||
}
|
||||
.col-2-3, .col-8-12 {
|
||||
width: 66.66%;
|
||||
}
|
||||
|
||||
.col-1-2, .col-6-12 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.col-1-3, .col-4-12 {
|
||||
width: 33.33%;
|
||||
}
|
||||
|
||||
.col-1-4, .col-3-12 {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.col-1-5 {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.col-1-6, .col-2-12 {
|
||||
width: 16.667%;
|
||||
}
|
||||
|
||||
.col-1-7 {
|
||||
width: 14.28%;
|
||||
}
|
||||
|
||||
.col-1-8 {
|
||||
width: 12.5%;
|
||||
}
|
||||
|
||||
.col-1-9 {
|
||||
width: 11.1%;
|
||||
}
|
||||
|
||||
.col-1-10 {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
.col-1-11 {
|
||||
width: 9.09%;
|
||||
}
|
||||
|
||||
.col-1-12 {
|
||||
width: 8.33%
|
||||
}
|
||||
|
||||
/* Layout Columns */
|
||||
|
||||
.col-11-12 {
|
||||
width: 91.66%
|
||||
}
|
||||
|
||||
.col-10-12 {
|
||||
width: 83.333%;
|
||||
}
|
||||
|
||||
.col-9-12 {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.col-5-12 {
|
||||
width: 41.66%;
|
||||
}
|
||||
|
||||
.col-7-12 {
|
||||
width: 58.33%
|
||||
}
|
||||
|
||||
/* Pushing blocks */
|
||||
|
||||
.push-2-3, .push-8-12 {
|
||||
margin-left: 66.66%;
|
||||
}
|
||||
|
||||
.push-1-2, .push-6-12 {
|
||||
margin-left: 50%;
|
||||
}
|
||||
|
||||
.push-1-3, .push-4-12 {
|
||||
margin-left: 33.33%;
|
||||
}
|
||||
|
||||
.push-1-4, .push-3-12 {
|
||||
margin-left: 25%;
|
||||
}
|
||||
|
||||
.push-1-5 {
|
||||
margin-left: 20%;
|
||||
}
|
||||
|
||||
.push-1-6, .push-2-12 {
|
||||
margin-left: 16.667%;
|
||||
}
|
||||
|
||||
.push-1-7 {
|
||||
margin-left: 14.28%;
|
||||
}
|
||||
|
||||
.push-1-8 {
|
||||
margin-left: 12.5%;
|
||||
}
|
||||
|
||||
.push-1-9 {
|
||||
margin-left: 11.1%;
|
||||
}
|
||||
|
||||
.push-1-10 {
|
||||
margin-left: 10%;
|
||||
}
|
||||
|
||||
.push-1-11 {
|
||||
margin-left: 9.09%;
|
||||
}
|
||||
|
||||
.push-1-12 {
|
||||
margin-left: 8.33%
|
||||
}
|
||||
|
||||
@media handheld, only screen and (max-width: 767px) {
|
||||
.grid {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
padding-left: 20px; /* grid-space to left */
|
||||
padding-right: 10px; /* grid-space to right: (grid-space-left - column-space) e.g. 20px-10px=10px */
|
||||
}
|
||||
|
||||
[class*='col-'] {
|
||||
width: auto;
|
||||
float: none;
|
||||
margin: 10px 0;
|
||||
padding-left: 0;
|
||||
padding-right: 10px; /* column-space */
|
||||
}
|
||||
|
||||
[class*='col-'] [class*='col-'] {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
/* Mobile Layout */
|
||||
|
||||
[class*='mobile-col-'] {
|
||||
float: left;
|
||||
margin: 0 0 10px;
|
||||
padding-left: 0;
|
||||
padding-right: 10px; /* column-space */
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.mobile-col-1-1 {
|
||||
width: 100%;
|
||||
}
|
||||
.mobile-col-2-3, .mobile-col-8-12 {
|
||||
width: 66.66%;
|
||||
}
|
||||
|
||||
.mobile-col-1-2, .mobile-col-6-12 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.mobile-col-1-3, .mobile-col-4-12 {
|
||||
width: 33.33%;
|
||||
}
|
||||
|
||||
.mobile-col-1-4, .mobile-col-3-12 {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.mobile-col-1-5 {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.mobile-col-1-6, .mobile-col-2-12 {
|
||||
width: 16.667%;
|
||||
}
|
||||
|
||||
.mobile-col-1-7 {
|
||||
width: 14.28%;
|
||||
}
|
||||
|
||||
.mobile-col-1-8 {
|
||||
width: 12.5%;
|
||||
}
|
||||
|
||||
.mobile-col-1-9 {
|
||||
width: 11.1%;
|
||||
}
|
||||
|
||||
.mobile-col-1-10 {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
.mobile-col-1-11 {
|
||||
width: 9.09%;
|
||||
}
|
||||
|
||||
.mobile-col-1-12 {
|
||||
width: 8.33%
|
||||
}
|
||||
|
||||
/* Layout Columns */
|
||||
|
||||
.mobile-col-11-12 {
|
||||
width: 91.66%
|
||||
}
|
||||
|
||||
.mobile-col-10-12 {
|
||||
width: 83.333%;
|
||||
}
|
||||
|
||||
.mobile-col-9-12 {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.mobile-col-5-12 {
|
||||
width: 41.66%;
|
||||
}
|
||||
|
||||
.mobile-col-7-12 {
|
||||
width: 58.33%
|
||||
}
|
||||
|
||||
.hide-on-mobile {
|
||||
display: none !important;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
562
imports/util/validator.js
Normal file
562
imports/util/validator.js
Normal file
@@ -0,0 +1,562 @@
|
||||
/*!
|
||||
* Validator v0.11.5 for Bootstrap 3, by @1000hz
|
||||
* Copyright 2016 Cina Saffary
|
||||
* Licensed under http://opensource.org/licenses/MIT
|
||||
*
|
||||
* https://github.com/1000hz/bootstrap-validator
|
||||
*/
|
||||
|
||||
/*!
|
||||
* Modified by Wynne Crisman 10/2016.
|
||||
* Modifications Copyright 2016 Wynne Crisman
|
||||
* Modifications licensed under http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Added commenting and formatting to tabs - wtf spaces?! (side note: If you are coding in Notepad or vim, please seek a therapist's help immediately. Perhaps you haven't heard that there are free tools (Notepad++, Emacs) that do formatting for you, or you can go all out and use an actual integrated development environment like a professional developer who values their time might.)
|
||||
* Added semicolons; While not strictly required, they help with readability (knowing, without thinking, where a line ends is really handy when you code all day) and are required by some pretty printing tools for JS.
|
||||
* Removed code that used && as an if block - this is really hard to read, and will confuse even seasoned developers - there is no reason to do it (any performance improvement is due entirely to crappy VM implementations - there is no hard evidence this is worth the aggravation).
|
||||
* Added an optional parameter to validate(..) - a callback function may be passed now. The callback will be provided with a boolean parameter 'isValid' indicating whether the form validated. This allows developers to easily force a validation and take an action on the result (submit the form?).
|
||||
* Added documentation at the top of this file to provide basic usage information.
|
||||
* Added highlighting on input-group elements in addition to form-group elements.
|
||||
*/
|
||||
|
||||
/*
|
||||
* USAGE INFO
|
||||
*
|
||||
* Getting Started:
|
||||
* Create a form along with any fields in HTML '<form id='myform'>...</form>', then in your javascript block call: $('#myform').validator(); to initialize the validator.
|
||||
* Your form elements may specify additional standard validation tags to help the validator know what to do (for example add 'required' to any form element to tell the validator which elements must have data).
|
||||
* Form elements should be put into form-groups, placing related elements into a common group. Form groups are simply Div's with the class='form-group'.
|
||||
* Useful Functions:
|
||||
* $(form).validator() - Initializes the validator for a form. This does not return the validator instance, but it does attach it to the form element.
|
||||
* $(form).data('bs.validator') - Gets the validator object for the given form. The validator object is stored as data attached to the form with the 'bs.validator' key.
|
||||
* validate(fn) - Forces validation to occur and takes an optional callback which will be passed a flag (boolean) indicating the success of the validation (isValid).
|
||||
* reset() - Resets the form's validation status. Clears all error information, without turning validation off.
|
||||
* update() - Updates the collection of fields that require validation. Call this after making changes to the form, including initializing any form elements that may generate HTML (such as Select2).
|
||||
*
|
||||
* Notes:
|
||||
* To handle decimal values "0.05", you need to add `step='0.01'` or similar to the input field.
|
||||
* To get the validator to validate a field, you must add `required` to the property list: `<input type='text' required/>`
|
||||
* I have modified this to not require a form-group or form-input classed container. If one is found, then the container will be used to mark for errors and success, otherwise the field element will be marked instead. Example: `<div class='form-group'><input type='text' required/></div>`
|
||||
*/
|
||||
|
||||
+function ($) {
|
||||
'use strict';
|
||||
|
||||
// VALIDATOR CLASS DEFINITION
|
||||
// ==========================
|
||||
|
||||
//Gets the value of the HTML element.
|
||||
function getValue($el) {
|
||||
if($el.is('[type="checkbox"]')) {
|
||||
return $el.prop('checked')
|
||||
}
|
||||
else if($el.is('[type="radio"]')) {
|
||||
return !!$('[name="' + $el.attr('name') + '"]:checked').length
|
||||
}
|
||||
else {
|
||||
return $el.val();
|
||||
}
|
||||
}
|
||||
|
||||
//Gets the actual element to perform the validation on. This returns the input element if the input element isn't a surrogate for a hidden form element.
|
||||
//Some widget libraries (such as Select2) hide the actual element that holds the data, and use custom graphical elements for the control and display logic.
|
||||
//We need the actual element that holds the value, and not the display elements which fire the events we are interested in (focusout, etc).
|
||||
function getActualWidget($el) {
|
||||
if($el.hasClass('select2-search__field')) { //Handle Select2 multi-select controls.
|
||||
//Select2 creates a structure of span elements whose parent span has the .select2 class and whose sibling is a hidden select element that contains the actual selection to be validated.
|
||||
return $el.parents('.select2').siblings('select');
|
||||
}
|
||||
else {
|
||||
return $el;
|
||||
}
|
||||
}
|
||||
//The opposite of getActualWidget($el). Gets the visible surrogate widget for the actual hidden form widget.
|
||||
//The actual hidden widget holds the value and acts like a form element when submitting the form, while the surrogate has the display and functionality desired by the view.
|
||||
function getSurrogate($el) {
|
||||
if($el.hasClass('select2-hidden-accessible')) { //Handle Select2 multi-select controls.
|
||||
//Select2 creates a structure of span elements whose parent span has the .select2 class and whose sibling is a hidden select element that contains the actual selection to be validated.
|
||||
return $el.siblings('.select2').find('.select2-selection');
|
||||
}
|
||||
else {
|
||||
return $el;
|
||||
}
|
||||
}
|
||||
|
||||
var Validator = function(element, options) {
|
||||
this.options = options;
|
||||
this.validators = $.extend({}, Validator.VALIDATORS, options.custom);
|
||||
this.$element = $(element);
|
||||
this.$btn = $('button[type="submit"], input[type="submit"]')
|
||||
.filter('[form="' + this.$element.attr('id') + '"]')
|
||||
.add(this.$element.find('input[type="submit"], button[type="submit"]'));
|
||||
|
||||
this.update();
|
||||
|
||||
//Register for the events (uses a namespace for easy de-registration).
|
||||
this.$element.on('input.bs.validator change.bs.validator focusout.bs.validator', $.proxy(this.onInput, this));
|
||||
this.$element.on('submit.bs.validator', $.proxy(this.onSubmit, this));
|
||||
this.$element.on('reset.bs.validator', $.proxy(this.reset, this));
|
||||
|
||||
//TODO: What is '[data-match]' ????
|
||||
//This will find some kind of matching elements in the form and when the validation event is detected on the match target (retrieved from the data-match element's 'match' data), a validation event will also be fired on the data-match element.
|
||||
this.$element.find('[data-match]').each(function() {
|
||||
var $this = $(this);
|
||||
var target = $this.data('match');
|
||||
|
||||
//Register an event handler on the match target element and if the match target element has a value, then fire the validator event on the data-match element also.
|
||||
$(target).on('input.bs.validator', function(e) {
|
||||
if(getValue($this)) $this.trigger('input.bs.validator');
|
||||
});
|
||||
});
|
||||
|
||||
//Force validation on any elements that start with values (ignore those that don't).
|
||||
//Filter the HTML elements we will be validating to get the set that contains values (ignore those that don't have values), then trigger a 'focusout' event on those elements (forcing validation as we start the Validator up).
|
||||
this.$inputs.filter(function() {
|
||||
return getValue($(this))
|
||||
}).trigger('focusout');
|
||||
|
||||
//Diable automatic native validation.
|
||||
this.$element.attr('novalidate', true);
|
||||
//Update the submit elements based on the current error state.
|
||||
this.toggleSubmit();
|
||||
};
|
||||
|
||||
Validator.VERSION = '0.11.5';
|
||||
|
||||
Validator.INPUT_SELECTOR = ':input:not([type="hidden"], [type="submit"], [type="reset"], button, .select2-search__field)'
|
||||
|
||||
Validator.SURROGATE_SELECTOR = '.select2 .select2-selection';
|
||||
|
||||
Validator.FOCUS_OFFSET = 20;
|
||||
|
||||
Validator.DEFAULTS = {
|
||||
delay: 500,
|
||||
html: false,
|
||||
disable: true,
|
||||
focus: true,
|
||||
custom: {},
|
||||
errors: {
|
||||
match: 'Does not match',
|
||||
minlength: 'Not long enough'
|
||||
},
|
||||
feedback: {
|
||||
success: 'glyphicon-ok',
|
||||
error: 'glyphicon-remove'
|
||||
}
|
||||
};
|
||||
|
||||
//The default set of validators to be used.
|
||||
Validator.VALIDATORS = {
|
||||
'native': function($el) {
|
||||
var el = $el[0];
|
||||
if(el.checkValidity) {
|
||||
return !el.checkValidity() && !el.validity.valid && (el.validationMessage || "error!");
|
||||
}
|
||||
},
|
||||
'match': function($el) {
|
||||
var target = $el.data('match');
|
||||
return $el.val() !== $(target).val() && Validator.DEFAULTS.errors.match;
|
||||
},
|
||||
'minlength': function($el) {
|
||||
var minlength = $el.data('minlength');
|
||||
return $el.val().length < minlength && Validator.DEFAULTS.errors.minlength;
|
||||
}
|
||||
};
|
||||
|
||||
//Collects a list of HTML elements that require validation using the INPUT_SELECTOR option supplied when the Validator was created, and the default data-validate attribute that can be supplied in the HTML tags.
|
||||
Validator.prototype.update = function() {
|
||||
this.$inputs = this.$element.find(Validator.INPUT_SELECTOR)
|
||||
.add(this.$element.find('[data-validate="true"]'))
|
||||
.not(this.$element.find('[data-validate="false"]'));
|
||||
this.$surrogates = this.$inputs.filter('.select2-hidden-accessible').map(function(el) {
|
||||
return getSurrogate($(this));
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
//An event handler that will perform validation on a HTML element when that element fires an event.
|
||||
Validator.prototype.onInput = function(e) {
|
||||
var self = this;
|
||||
var $surrogate = $(e.target); //Wrapper the event target with a jquery object.
|
||||
var $el = getActualWidget($surrogate); //Get the actual (hidden) form widget if there is one, otherwise $el will equal (===) $surrogate.
|
||||
var deferErrors = e.type !== 'focusout';
|
||||
|
||||
//If the event target is not in the set of HTML elements that require validation, then ignore it.
|
||||
if(!this.$inputs.is($el)) return;
|
||||
|
||||
//Validate the HTML element and update the submit button's state as necessary.
|
||||
this.validateInput($el, $surrogate, deferErrors).done(function() {
|
||||
self.toggleSubmit();
|
||||
})
|
||||
};
|
||||
|
||||
//Runs a complete validation. Returns this validator (does not return the result of the validation since the validation is performed in the future).
|
||||
//@param fn An optional function that will be run after validating, and which will be passed a boolean indicating whether the form passed validation (isValid). [Added by Wynne Crisman 10/2016]
|
||||
// Allows the user to validate and do something based on the form's validity without any complicated gyrations:
|
||||
// $form.data('bs.validator').validate(function(isValid) { if(isValid) { /* Allow form submittal. */ }});
|
||||
Validator.prototype.validate = function(fn) {
|
||||
var self = this;
|
||||
|
||||
//Create a collection of promises by validating each input HTML element, then update the submit logic based on the presence of errors and set the focus to the first element with an error.
|
||||
$.when(this.$inputs.map(function(el) {
|
||||
var $el = $(this); //Wrapper the event target with a jquery object.
|
||||
var $surrogate = getSurrogate($el); //Gets the surrogate widget used to manage the display and functionality. Will === $el if there isn't a surrogate.
|
||||
|
||||
return self.validateInput($el, $surrogate, false);
|
||||
})).then(function() {
|
||||
self.toggleSubmit();
|
||||
self.focusError();
|
||||
|
||||
//Call the callback, passing whether the form is valid (boolean).
|
||||
if(fn instanceof Function) fn(!self.isIncomplete() && !self.hasErrors());
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
//Validates the value of an HTML element and returns a promise.
|
||||
Validator.prototype.validateInput = function($el, $surrogate, deferErrors) {
|
||||
//var value = getValue($el);
|
||||
var prevErrors = $el.data('bs.validator.errors'); //Get the errors from a previous run of the validator.
|
||||
var errors;
|
||||
|
||||
if($el.is('[type="radio"]')) $el = this.$element.find('input[name="' + $el.attr('name') + '"]');
|
||||
|
||||
//Create a validator event indicating we are about to validate.
|
||||
var e = $.Event('validate.bs.validator', {relatedTarget: $el[0]});
|
||||
//Fire the event.
|
||||
this.$element.trigger(e);
|
||||
//If the event handlers flag that we shouldn't validate then stop here.
|
||||
if(e.isDefaultPrevented()) return;
|
||||
|
||||
var self = this;
|
||||
|
||||
//Run the validators on our HTML element and handle any errors.
|
||||
return this.runValidators($el).done(function(errors) {
|
||||
//Save the errors by attaching them to the HTML element.
|
||||
$el.data('bs.validator.errors', errors);
|
||||
|
||||
//If there were no errors then call clearErrors() to remove the error styling on the view, otherwise we do have errors and we either show them immediately (change styling & HTML) or defer the showing of them until later if told to defer.
|
||||
!errors.length ? self.clearErrors($el) : (deferErrors ? self.defer($el, self.showErrors) : self.showErrors($el));
|
||||
|
||||
//If this is the first run of the validator for this element (prevErrors == undefined), or the previous errors are the same as the new errors (nothing changed), then fire an event notifying listeners of the change in error status.
|
||||
if(!prevErrors || errors.toString() !== prevErrors.toString()) {
|
||||
if(errors.length) {
|
||||
e = $.Event('invalid.bs.validator', {relatedTarget: $el[0], detail: errors});
|
||||
}
|
||||
else {
|
||||
e = $.Event('valid.bs.validator', {relatedTarget: $el[0], detail: prevErrors});
|
||||
}
|
||||
|
||||
//Fire the event.
|
||||
self.$element.trigger(e);
|
||||
}
|
||||
|
||||
//Update the view's submit elements based on the error state of the form.
|
||||
self.toggleSubmit();
|
||||
//Fire an event on the form indicating the related element was validated (irregardless of whether it has errors or not).
|
||||
self.$element.trigger($.Event('validated.bs.validator', {relatedTarget: $el[0]}));
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
//Returns a Promise where the result is the array of errors (error messages) found by the validator. The promise will be passed an empty array if there are no errors.
|
||||
Validator.prototype.runValidators = function($el) {
|
||||
var errors = [];
|
||||
var deferred = $.Deferred();
|
||||
|
||||
//If using deferred validation then reject (avoid recursive calls?).
|
||||
if($el.data('bs.validator.deferred')) $el.data('bs.validator.deferred').reject();
|
||||
//Set that validation is deferred (avoid recursive calls?).
|
||||
$el.data('bs.validator.deferred', deferred);
|
||||
|
||||
function getValidatorSpecificError(key) {
|
||||
return $el.data(key + '-error');
|
||||
}
|
||||
|
||||
function getValidityStateError() {
|
||||
var validity = $el[0].validity;
|
||||
return validity.typeMismatch ? $el.data('type-error')
|
||||
: validity.patternMismatch ? $el.data('pattern-error')
|
||||
: validity.stepMismatch ? $el.data('step-error')
|
||||
: validity.rangeOverflow ? $el.data('max-error')
|
||||
: validity.rangeUnderflow ? $el.data('min-error')
|
||||
: validity.valueMissing ? $el.data('required-error')
|
||||
: null;
|
||||
}
|
||||
|
||||
function getGenericError() {
|
||||
return $el.data('error');
|
||||
}
|
||||
|
||||
function getErrorMessage(key) {
|
||||
return getValidatorSpecificError(key)
|
||||
|| getValidityStateError()
|
||||
|| getGenericError();
|
||||
}
|
||||
|
||||
//For each validator in the validator hashmap (key value pair), call the function f(key,validator) using this as the context (so it has access to 'this' from this method).
|
||||
//See the VALIDATORS hashmap defined above (used as the default set of validator functions).
|
||||
$.each(this.validators, $.proxy(function(key, validator) {
|
||||
var error;
|
||||
|
||||
//If the HTML element has a value OR is required, AND there is data attached to the HTML element for the current validator OR the validator is 'native', AND the validator produces an error.
|
||||
if((getValue($el) || $el.attr('required')) && ($el.data(key) || key == 'native') && (error = validator.call(this, $el))) {
|
||||
//Allow generic or state or specific validator errors to trump the validation error.
|
||||
error = getErrorMessage(key) || error;
|
||||
|
||||
//If the error is not in the list then add it. Use jquery.inArray instead of indexOf since indexOf does not exist in some IE versions.
|
||||
if(!~$.inArray(error, errors)) errors.push(error);
|
||||
}
|
||||
}, this));
|
||||
|
||||
//Get errors from the server if a 'remote' URL is provided (allow it to check the element's value and supply additional errors), and then resolve the promise with the collected errors.
|
||||
//If there are no errors AND the HTML element has a value AND the HTML element has 'remote' data associated, then make an AJAX call sometime in the future to pass the server the element name and value, otherwise resolve the promise passing the error list.
|
||||
if(!errors.length && getValue($el) && $el.data('remote')) {
|
||||
this.defer($el, function() {
|
||||
var data = {};
|
||||
|
||||
//Assign elementName = elementValue to the data object.
|
||||
data[$el.attr('name')] = getValue($el);
|
||||
//Make an AJAX GET call to the URL stored in the element's 'remote' data, passing the object containing the element's name and its value. If there is an error in the AJAX call then add the error to the error list.
|
||||
$.get($el.data('remote'), data)
|
||||
.fail(function(jqXHR, textStatus, error) {
|
||||
errors.push(getErrorMessage('remote') || error)
|
||||
})
|
||||
.always(function() {
|
||||
deferred.resolve(errors)
|
||||
})
|
||||
})
|
||||
}
|
||||
else deferred.resolve(errors);
|
||||
|
||||
//Return the promise that will be passed the collected errors (array of strings) when the error checking is complete.
|
||||
return deferred.promise();
|
||||
};
|
||||
|
||||
//Changes the focus to the first element with an error.
|
||||
Validator.prototype.focusError = function() {
|
||||
if(!this.options.focus) return;
|
||||
|
||||
var $input = this.$element.find(".has-error:first"); // :input
|
||||
|
||||
if($input.length === 0) return;
|
||||
//Find the input field if it is a input group or form group that has the error markings.
|
||||
if($input.hasClass('form-group') || $input.hasClass('input-group')) {
|
||||
$input = $input.find('input');
|
||||
}
|
||||
|
||||
//If this is a select2 control then look for the child of a sibling that has the .select2-selection class and use it instead.
|
||||
if($input.hasClass('select2-hidden-accessible')) {
|
||||
$input = $input.parent().find('.select2-selection');
|
||||
}
|
||||
|
||||
$('html, body').animate({scrollTop: $input.offset().top - Validator.FOCUS_OFFSET}, 250);
|
||||
$input.focus();
|
||||
};
|
||||
|
||||
//Alters the display to highlight errors in the form.
|
||||
Validator.prototype.showErrors = function($el) {
|
||||
var method = this.options.html ? 'html' : 'text';
|
||||
var errors = $el.data('bs.validator.errors');
|
||||
var $group = $el.closest('.form-group,.input-group');
|
||||
var $block = $group.find('.help-block.with-errors');
|
||||
var $feedback = $group.find('.form-control-feedback');
|
||||
|
||||
if(!errors.length) return;
|
||||
|
||||
errors = $('<ul/>')
|
||||
.addClass('list-unstyled')
|
||||
.append($.map(errors, function(error) {
|
||||
return $('<li/>')[method](error)
|
||||
}));
|
||||
|
||||
if($block.data('bs.validator.originalContent') === undefined)
|
||||
$block.data('bs.validator.originalContent', $block.html());
|
||||
|
||||
$block.empty().append(errors);
|
||||
|
||||
if($group.length > 0)
|
||||
//Add the 'has-error' and 'has-danger' classes to the grouping.
|
||||
$group.addClass('has-error has-danger');
|
||||
else
|
||||
$el.addClass('has-error has-danger');
|
||||
|
||||
//If this is a select2 control then look for the child of a sibling that has the .select2-selection class and use it instead.
|
||||
if($el.hasClass('select2-hidden-accessible')) {
|
||||
$el.parent().find('.select2-selection').addClass('has-error has-danger');
|
||||
}
|
||||
|
||||
//If the group has the 'has-feedback' class then remove any success markings and add error markings.
|
||||
if($group.hasClass('has-feedback')) {
|
||||
$feedback.removeClass(this.options.feedback.success);
|
||||
$feedback.addClass(this.options.feedback.error);
|
||||
$group.removeClass('has-success');
|
||||
}
|
||||
};
|
||||
|
||||
//Clears the display of all error information.
|
||||
Validator.prototype.clearErrors = function($el) {
|
||||
var $group = $el.closest('.form-group,.input-group');
|
||||
var $block = $group.find('.help-block.with-errors');
|
||||
var $feedback = $group.find('.form-control-feedback');
|
||||
|
||||
$block.html($block.data('bs.validator.originalContent'));
|
||||
|
||||
if($group.length > 0)
|
||||
$group.removeClass('has-error has-danger has-success');
|
||||
else
|
||||
$el.removeClass('has-error has-danger has-success');
|
||||
|
||||
//Clean the sibling controls for select2.
|
||||
$el.parent().find('.select2 .select2-selection').removeClass('has-error has-danger has-success');
|
||||
|
||||
$group.hasClass('has-feedback')
|
||||
&& $feedback.removeClass(this.options.feedback.error)
|
||||
&& $feedback.removeClass(this.options.feedback.success)
|
||||
&& getValue($el)
|
||||
&& $feedback.addClass(this.options.feedback.success)
|
||||
&& $group.addClass('has-success');
|
||||
};
|
||||
|
||||
//Returns whether the form has any errors.
|
||||
//Warning: Calling this externally is dangerous because validation is asynchronous and this call will not wait for validation to finish.
|
||||
// Better to call '$form.data('bs.validator').validate(function(isValid) {...})' instead to ensure validation is done.
|
||||
Validator.prototype.hasErrors = function() {
|
||||
function fieldErrors() {
|
||||
return !!($(this).data('bs.validator.errors') || []).length;
|
||||
}
|
||||
|
||||
return !!this.$inputs.filter(fieldErrors).length;
|
||||
};
|
||||
|
||||
//Returns whether the form is not complete (appears this is due to a text input containing only spaces when a value is required).
|
||||
//Warning: Calling this externally is dangerous because validation is asynchronous and this call will not wait for validation to finish.
|
||||
// Better to call '$form.data('bs.validator').validate(function(isValid) {...})' instead to ensure validation is done.
|
||||
Validator.prototype.isIncomplete = function() {
|
||||
function fieldIncomplete() {
|
||||
var value = getValue($(this));
|
||||
return !(typeof value == "string" ? $.trim(value) : value);
|
||||
}
|
||||
|
||||
return !!this.$inputs.filter('[required]').filter(fieldIncomplete).length;
|
||||
};
|
||||
|
||||
//Prevents form submittal (by setting the preventDefault flag on the event) if the form is not valid or complete.
|
||||
Validator.prototype.onSubmit = function(e) {
|
||||
this.validate();
|
||||
if(this.isIncomplete() || this.hasErrors()) e.preventDefault();
|
||||
};
|
||||
|
||||
//Enables or disables the submit button based on the validation state of the form.
|
||||
Validator.prototype.toggleSubmit = function() {
|
||||
if(!this.options.disable) return;
|
||||
this.$btn.toggleClass('disabled', this.isIncomplete() || this.hasErrors())
|
||||
};
|
||||
|
||||
//Defers the callback function so that it runs sometime in the future. Only one callback may be deferred at any given time. How far into the future it will be deferred depends on the 'delay' option.
|
||||
//Used internally to defer validation as the user interacts with the form such that we don't validated constantly (for every character typed).
|
||||
Validator.prototype.defer = function($el, callback) {
|
||||
//Wrapper the callback so it's 'this' variable will be the validator.
|
||||
callback = $.proxy(callback, this, $el);
|
||||
//If there isn't a delay then run the callback immediately.
|
||||
if(!this.options.delay) return callback();
|
||||
//Clear any callback already being delayed.
|
||||
window.clearTimeout($el.data('bs.validator.timeout'));
|
||||
//Delay the execution of the callback.
|
||||
$el.data('bs.validator.timeout', window.setTimeout(callback, this.options.delay));
|
||||
};
|
||||
|
||||
//Resets the form to its state, removing all validator modifications (but not removing the validator). Returns the validator reference.
|
||||
Validator.prototype.reset = function() {
|
||||
this.$element.find('.form-control-feedback').removeClass(this.options.feedback.error).removeClass(this.options.feedback.success);
|
||||
|
||||
this.$inputs
|
||||
.removeData(['bs.validator.errors', 'bs.validator.deferred'])
|
||||
.each(function() {
|
||||
var $this = $(this);
|
||||
var timeout = $this.data('bs.validator.timeout');
|
||||
window.clearTimeout(timeout) && $this.removeData('bs.validator.timeout');
|
||||
});
|
||||
|
||||
this.$element.find('.help-block.with-errors')
|
||||
.each(function() {
|
||||
var $this = $(this);
|
||||
var originalContent = $this.data('bs.validator.originalContent');
|
||||
|
||||
$this
|
||||
.removeData('bs.validator.originalContent')
|
||||
.html(originalContent);
|
||||
});
|
||||
|
||||
this.$btn.removeClass('disabled');
|
||||
this.$element.find('.has-error, .has-danger, .has-success').removeClass('has-error has-danger has-success');
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
//Removes the validator completely and resets the state.
|
||||
Validator.prototype.destroy = function() {
|
||||
this.reset();
|
||||
|
||||
//Remove attributes, data, and event handlers from all elements.
|
||||
this.$element
|
||||
.removeAttr('novalidate')
|
||||
.removeData('bs.validator')
|
||||
.off('.bs.validator');
|
||||
|
||||
//Remove event handlers from input elements.
|
||||
this.$inputs.off('.bs.validator');
|
||||
|
||||
this.options = null;
|
||||
this.validators = null;
|
||||
this.$element = null;
|
||||
this.$btn = null;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// VALIDATOR PLUGIN DEFINITION
|
||||
// ===========================
|
||||
|
||||
|
||||
//Plugin constructor.
|
||||
function Plugin(option) {
|
||||
return this.each(function() {
|
||||
var $this = $(this);
|
||||
var options = $.extend({}, Validator.DEFAULTS, $this.data(), typeof option == 'object' && option);
|
||||
var data = $this.data('bs.validator');
|
||||
|
||||
if(!data && option == 'destroy') return;
|
||||
if(!data) $this.data('bs.validator', (data = new Validator(this, options)));
|
||||
if(typeof option == 'string') data[option]();
|
||||
});
|
||||
}
|
||||
|
||||
var old = $.fn.validator;
|
||||
|
||||
//JQuery integration.
|
||||
$.fn.validator = Plugin;
|
||||
$.fn.validator.Constructor = Validator;
|
||||
|
||||
|
||||
// VALIDATOR NO CONFLICT
|
||||
// =====================
|
||||
|
||||
$.fn.validator.noConflict = function() {
|
||||
$.fn.validator = old;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
// VALIDATOR DATA-API
|
||||
// ==================
|
||||
|
||||
$(window).on('load', function() {
|
||||
$('form[data-toggle="validator"]').each(function() {
|
||||
var $form = $(this);
|
||||
Plugin.call($form, $form.data());
|
||||
});
|
||||
});
|
||||
}(jQuery);
|
||||
Reference in New Issue
Block a user