├── .nvmrc ├── .npmrc ├── .husky └── pre-commit ├── .gitignore ├── assets ├── images │ ├── timer.png │ └── timer_long.png └── js │ ├── service.js │ └── syotimer.examples.js ├── .gitattributes ├── .prettierrc.json ├── tsconfig.lib.json ├── vite.config.ts ├── tsconfig.spec.json ├── index.d.ts ├── tsconfig.json ├── .github └── workflows │ ├── lint.yml │ └── issue_stale.yml ├── .eslintrc.json ├── source ├── utils.spec.ts ├── localization.ts ├── index.ts ├── constants.ts ├── types.ts ├── utils.ts └── SyoTimer.ts ├── LICENSE ├── rollup.config.mjs ├── package.json ├── CHANGELOG.md ├── resources └── default.css ├── README.md └── index.html /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/hydrogen -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | node_modules/ 4 | .idea/ 5 | npm-debug.log 6 | tmp/ -------------------------------------------------------------------------------- /assets/images/timer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrfratello/SyoTimer/HEAD/assets/images/timer.png -------------------------------------------------------------------------------- /assets/images/timer_long.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrfratello/SyoTimer/HEAD/assets/images/timer_long.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | build/* text -diff 4 | yarn.lock text -diff 5 | package-lock.json text -diff 6 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/prettierrc", 3 | "printWidth": 100, 4 | "singleQuote": true, 5 | "trailingComma": "all" 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./tmp/out-tsc" 5 | }, 6 | "include": ["source/**/*.ts", "index.d.ts"], 7 | "exclude": ["vite.config.ts", "source/**/*.spec.ts", "source/**/*.test.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | cache: { 7 | dir: './node_modules/.vitest', 8 | }, 9 | environment: 'jsdom', 10 | include: ['source/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}'], 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./tmp/out-tsc", 5 | "types": ["vitest/globals", "vitest/importMeta", "vite/client"] 6 | }, 7 | "include": [ 8 | "vite.config.ts", 9 | "vitest.setup.ts", 10 | "source/**/*.test.ts", 11 | "source/**/*.spec.ts", 12 | "index.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | SyoTimerOptions, 3 | SyoTimerLocalization, 4 | SyoTimerMethods, 5 | SyoTimerOptionProps, 6 | SyoTimerOptionValues, 7 | } from './source/types'; 8 | 9 | export type { SyoTimerOptions, SyoTimerLocalization }; 10 | 11 | declare global { 12 | interface JQuery { 13 | syotimer(options: SyoTimerOptions): JQuery; 14 | syotimer( 15 | method: SyoTimerMethods, 16 | property: SyoTimerOptionProps, 17 | value: SyoTimerOptionValues, 18 | ): JQuery; 19 | } 20 | 21 | interface JQueryStatic { 22 | syotimerLang: SyoTimerLocalization; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "baseUrl": "./source", 7 | "isolatedModules": true, 8 | "allowSyntheticDefaultImports": true, 9 | "esModuleInterop": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "resolveJsonModule": true, 12 | "strict": true, 13 | "noImplicitAny": true, 14 | "skipDefaultLibCheck": true, 15 | "skipLibCheck": true 16 | }, 17 | "files": [], 18 | "include": [], 19 | "references": [ 20 | { 21 | "path": "./tsconfig.lib.json" 22 | }, 23 | { 24 | "path": "./tsconfig.spec.json" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /assets/js/service.js: -------------------------------------------------------------------------------- 1 | jQuery(function ($) { 2 | $("pre") 3 | .hide() 4 | .before( 5 | '
' 6 | ); 7 | 8 | $(".toggle-source-code").on("click", function () { 9 | var button = $(this), 10 | code = button.next("pre"); 11 | if (button.hasClass("toggle-source-code_action_show")) { 12 | button 13 | .removeClass("toggle-source-code_action_show") 14 | .addClass("toggle-source-code_action_hide"); 15 | code.slideDown(); 16 | } else if (button.hasClass("toggle-source-code_action_hide")) { 17 | button 18 | .removeClass("toggle-source-code_action_hide") 19 | .addClass("toggle-source-code_action_show"); 20 | code.slideUp(); 21 | } 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | main: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: get sources 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | - name: node version 18 | id: node_info 19 | run: echo "version=$(cat .nvmrc)" >> $GITHUB_OUTPUT 20 | shell: bash 21 | - name: setup node lts version 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: '${{ steps.node_info.outputs.version }}' 25 | - name: install dependencies 26 | run: npm ci 27 | shell: bash 28 | 29 | - name: lint 30 | run: npm run lint 31 | - name: test 32 | run: npm run test 33 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "jquery": true 6 | }, 7 | "extends": ["airbnb-base", "airbnb-typescript/base", "prettier"], 8 | "parser": "@typescript-eslint/parser", 9 | "parserOptions": { 10 | "ecmaVersion": 12, 11 | "sourceType": "module", 12 | "project": "./tsconfig.*?.json" 13 | }, 14 | "plugins": ["@typescript-eslint", "prettier"], 15 | "rules": { 16 | "prettier/prettier": "error" 17 | }, 18 | "overrides": [ 19 | { 20 | "files": "{rollup.config.mjs,vite.config.ts}", 21 | "rules": { 22 | "import/no-extraneous-dependencies": [ 23 | "error", 24 | { 25 | "devDependencies": true, 26 | "optionalDependencies": false 27 | } 28 | ] 29 | } 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /source/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { format2 } from './utils'; 2 | 3 | describe('utils', () => { 4 | describe('format2', () => { 5 | test.each([ 6 | { input: 0, output: '0' }, 7 | { input: 3, output: '3' }, 8 | { input: 10, output: '10' }, 9 | { input: 21, output: '21' }, 10 | { input: 99, output: '99' }, 11 | ])('should return without lead zero: from $input to $output', ({ input, output }) => { 12 | expect(format2(input)).toBe(output); 13 | }); 14 | 15 | test.each([ 16 | { input: 0, output: '00' }, 17 | { input: 3, output: '03' }, 18 | { input: 10, output: '10' }, 19 | { input: 21, output: '21' }, 20 | { input: 99, output: '99' }, 21 | ])('should return with lead zero: from $input to $output', ({ input, output }) => { 22 | expect(format2(input, true)).toBe(output); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /.github/workflows/issue_stale.yml: -------------------------------------------------------------------------------- 1 | name: Stale 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | # This runs every day 20 minutes before midnight: https://crontab.guru/#40_23_*_*_* 7 | - cron: '40 23 * * *' 8 | 9 | jobs: 10 | stale: 11 | runs-on: ubuntu-latest 12 | if: github.repository_owner == 'mrfratello' 13 | permissions: 14 | issues: write 15 | pull-requests: write 16 | 17 | steps: 18 | - uses: actions/stale@v9 19 | name: 'Close stale issues' 20 | with: 21 | stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days.' 22 | stale-pr-message: 'This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days.' 23 | close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.' 24 | close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.' 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /source/localization.ts: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | 3 | $.syotimerLang = { 4 | rus: { 5 | second: ['секунда', 'секунды', 'секунд'], 6 | minute: ['минута', 'минуты', 'минут'], 7 | hour: ['час', 'часа', 'часов'], 8 | day: ['день', 'дня', 'дней'], 9 | handler: function rusNumeral(number, words) { 10 | const cases = [2, 0, 1, 1, 1, 2]; 11 | if (number % 100 > 4 && number % 100 < 20) { 12 | return words[2]; 13 | } 14 | const index = cases[number % 10 < 5 ? number % 10 : 5]; 15 | return words[index]; 16 | }, 17 | }, 18 | eng: { 19 | second: ['second', 'seconds'], 20 | minute: ['minute', 'minutes'], 21 | hour: ['hour', 'hours'], 22 | day: ['day', 'days'], 23 | }, 24 | por: { 25 | second: ['segundo', 'segundos'], 26 | minute: ['minuto', 'minutos'], 27 | hour: ['hora', 'horas'], 28 | day: ['dia', 'dias'], 29 | }, 30 | spa: { 31 | second: ['segundo', 'segundos'], 32 | minute: ['minuto', 'minutos'], 33 | hour: ['hora', 'horas'], 34 | day: ['día', 'días'], 35 | }, 36 | heb: { 37 | second: ['שניה', 'שניות'], 38 | minute: ['דקה', 'דקות'], 39 | hour: ['שעה', 'שעות'], 40 | day: ['יום', 'ימים'], 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /source/index.ts: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import './localization'; 3 | import mapSyoTimer from './SyoTimer'; 4 | import type { 5 | SyoTimerOptions, 6 | SyoTimerMethods, 7 | SyoTimerOptionProps, 8 | SyoTimerOptionValues, 9 | } from './types'; 10 | 11 | const methods: Record = { 12 | setOption(name: SyoTimerOptionProps, value: SyoTimerOptionValues) { 13 | const elementBox = $(this); 14 | const options = elementBox.data('syotimer-options'); 15 | if (Object.prototype.hasOwnProperty.call(options, name)) { 16 | options[name] = value; 17 | elementBox.data('syotimer-options', options); 18 | } 19 | }, 20 | }; 21 | 22 | $.fn.extend({ 23 | syotimer( 24 | this: JQuery, 25 | options: SyoTimerOptions | SyoTimerMethods, 26 | property: SyoTimerOptionProps, 27 | value: SyoTimerOptionValues, 28 | ) { 29 | if (typeof options === 'string' && options === 'setOption') { 30 | return this.each(function method() { 31 | methods[options].apply(this, [property, value]); 32 | }); 33 | } 34 | if (options === null || options === undefined || typeof options === 'object') { 35 | return mapSyoTimer(this, options); 36 | } 37 | return $.error('SyoTimer. Error in call methods: methods is not exist'); 38 | }, 39 | }); 40 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import terser from '@rollup/plugin-terser'; 2 | import typescript from '@rollup/plugin-typescript'; 3 | import dts from 'rollup-plugin-dts'; 4 | import pkg from './package.json' assert { type: 'json' }; 5 | 6 | export default [ 7 | { 8 | input: 'source/index.ts', 9 | external: ['jquery'], 10 | output: [ 11 | { 12 | file: './build/jquery.syotimer.js', 13 | format: 'iife', 14 | globals: { 15 | jquery: 'jQuery', 16 | }, 17 | banner: `/** 18 | * ${pkg.projectName} - ${pkg.description} 19 | * @version: ${pkg.version} 20 | * @author: ${pkg.author} 21 | * @homepage: ${pkg.homepage} 22 | * @repository: ${pkg.repository.url} 23 | * @license: under MIT license 24 | */`, 25 | }, 26 | { 27 | file: './build/jquery.syotimer.min.js', 28 | format: 'iife', 29 | globals: { 30 | jquery: 'jQuery', 31 | }, 32 | sourcemap: true, 33 | banner: `/** 34 | * ${pkg.projectName} v.${pkg.version} | under MIT license 35 | * ${pkg.homepage} 36 | */`, 37 | 38 | plugins: [ 39 | terser({ 40 | output: { 41 | comments: (_node, { type, value }) => type === 'comment2' && /license/i.test(value), 42 | }, 43 | }), 44 | ], 45 | }, 46 | ], 47 | plugins: [ 48 | typescript({ 49 | tsconfig: './tsconfig.lib.json', 50 | }), 51 | ], 52 | }, 53 | { 54 | input: './index.d.ts', 55 | output: [{ file: 'build/jquery.syotimer.d.ts', format: 'es' }], 56 | plugins: [dts()], 57 | }, 58 | ]; 59 | -------------------------------------------------------------------------------- /source/constants.ts: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import type { SyoTimerInternalOptions, ItemsHas, LinkedList, UnitLong, UnitShort } from './types'; 3 | 4 | export const DAY: UnitLong = 'day'; 5 | export const HOUR: UnitLong = 'hour'; 6 | export const MINUTE: UnitLong = 'minute'; 7 | export const SECOND: UnitLong = 'second'; 8 | export const DAY_IN_SEC = 24 * 60 * 60; 9 | export const HOUR_IN_SEC = 60 * 60; 10 | export const MINUTE_IN_SEC = 60; 11 | export const LAYOUT_TYPES: Record = { 12 | d: DAY, 13 | h: HOUR, 14 | m: MINUTE, 15 | s: SECOND, 16 | }; 17 | 18 | export const unitLinkedList: LinkedList = { 19 | list: [SECOND, MINUTE, HOUR, DAY], 20 | next(current) { 21 | const currentIndex = this.list.indexOf(current); 22 | return currentIndex < this.list.length ? this.list[currentIndex + 1] : null; 23 | }, 24 | prev(current) { 25 | const currentIndex = this.list.indexOf(current); 26 | return currentIndex > 0 ? this.list[currentIndex - 1] : null; 27 | }, 28 | }; 29 | 30 | export const defaultItemsHas: ItemsHas = { 31 | second: false, 32 | minute: false, 33 | hour: false, 34 | day: false, 35 | }; 36 | 37 | export const defaultOptions: SyoTimerInternalOptions = { 38 | date: 0, 39 | layout: 'dhms', 40 | periodic: false, 41 | periodInterval: 7, 42 | periodUnit: 'd', 43 | doubleNumbers: true, 44 | effectType: 'none', 45 | lang: 'eng', 46 | headTitle: '', 47 | footTitle: '', 48 | afterDeadline: (timerBlock) => { 49 | timerBlock.bodyBlock.html('

The countdown is finished!

'); 50 | }, 51 | itemTypes: ['day', 'hour', 'minute', 'second'], 52 | itemsHas: $.extend({}, defaultItemsHas), 53 | }; 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-syotimer", 3 | "projectName": "SyoTimer", 4 | "version": "3.1.2", 5 | "description": "jquery countdown plugin", 6 | "main": "./build/jquery.syotimer.js", 7 | "types": "./build/jquery.syotimer.d.ts", 8 | "files": [ 9 | "build", 10 | "resources" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/mrfratello/SyoTimer.git" 15 | }, 16 | "scripts": { 17 | "start": "rollup -c -w", 18 | "build": "rollup -c", 19 | "format": "prettier --write \"./source/\"", 20 | "format:check": "prettier --check \"./source/\"", 21 | "eslint": "eslint source/**", 22 | "tslint": "tsc -b --incremental", 23 | "lint": "npm run format:check && npm run tslint && npm run eslint", 24 | "test": "vitest --run", 25 | "test:ui": "vitest --ui", 26 | "release:before": "npm run lint && npm run build && git add .", 27 | "release": "standard-version -a", 28 | "prepare": "husky install" 29 | }, 30 | "keywords": [ 31 | "countdown", 32 | "jquery", 33 | "plugin" 34 | ], 35 | "author": "John Syomochkin ", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/mrfratello/SyoTimer/issues" 39 | }, 40 | "homepage": "https://mrfratello.github.io/SyoTimer", 41 | "standard-version": { 42 | "scripts": { 43 | "prerelease": "npm run release:before" 44 | } 45 | }, 46 | "devDependencies": { 47 | "@rollup/plugin-terser": "0.4.4", 48 | "@rollup/plugin-typescript": "11.1.6", 49 | "@types/jquery": "3.5.29", 50 | "@typescript-eslint/eslint-plugin": "6.21.0", 51 | "@typescript-eslint/parser": "6.21.0", 52 | "@vitest/ui": "1.3.1", 53 | "eslint": "8.56.0", 54 | "eslint-config-airbnb-base": "15.0.0", 55 | "eslint-config-airbnb-typescript": "17.1.0", 56 | "eslint-config-prettier": "9.1.0", 57 | "eslint-plugin-import": "2.29.1", 58 | "eslint-plugin-prettier": "5.1.3", 59 | "husky": "9.0.11", 60 | "jquery": "3.7.1", 61 | "jsdom": "24.0.0", 62 | "prettier": "3.2.5", 63 | "rollup": "4.12.0", 64 | "rollup-plugin-dts": "6.1.0", 65 | "standard-version": "9.5.0", 66 | "tslib": "2.6.2", 67 | "typescript": "5.3.3", 68 | "vite": "5.1.4", 69 | "vitest": "1.3.1" 70 | }, 71 | "peerDependencies": { 72 | "jquery": "^1.12 || ^2.0 || ^3.0" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [3.1.2](https://github.com/mrfratello/SyoTimer/compare/v3.1.1...v3.1.2) (2024-02-25) 6 | 7 | ### [3.1.1](https://github.com/mrfratello/SyoTimer/compare/v3.1.0...v3.1.1) (2021-09-15) 8 | 9 | ## [3.1.0](https://github.com/mrfratello/SyoTimer/compare/v3.0.0...v3.1.0) (2021-09-10) 10 | 11 | 12 | ### Features 13 | 14 | * add typescript and refactor source structure ([b1398c5](https://github.com/mrfratello/SyoTimer/commit/b1398c59978a2c91ae13f6aff29feade7b5d527f)) 15 | 16 | 17 | ### Bug Fixes 18 | 19 | * use correct font types in default styles ([ffb3a78](https://github.com/mrfratello/SyoTimer/commit/ffb3a78f435bb3c764d39dbf80fe954200353958)) 20 | 21 | ## [3.0.0](https://github.com/mrfratello/SyoTimer/compare/v2.1.3...v3.0.0) (2021-08-31) 22 | 23 | 24 | ### ⚠ BREAKING CHANGES 25 | 26 | * `$.syotimerLang[lang].handler` must be function with two arguments 27 | * API Changed: 28 | 29 | * use date (number, Date) property instead year, month, day, hour, minute, second props 30 | * remove properties timeZone and ignoreTransferTime 31 | 32 | ### Features 33 | 34 | * change api, update exaples ([d1abea1](https://github.com/mrfratello/SyoTimer/commit/d1abea14432e6fb4e86cbf8cc2499dc0eeb17e67)) 35 | * change localization api ([cdd3436](https://github.com/mrfratello/SyoTimer/commit/cdd34366b18564f1880f53f3479f51cbd764e1ce)) 36 | 37 | ### [2.1.1](https://github.com/mrfratello/SyoTimer/compare/v2.0.0...v2.1.1) (2019-10-17) 38 | 39 | ### Features 40 | 41 | * publish on npm 42 | * used universal module definition 43 | * added default CSS styles 44 | 45 | ### [2.0.0](https://github.com/mrfratello/SyoTimer/compare/v1.1.0...v2.0.0) (2017-06-24) 46 | 47 | ### Features 48 | 49 | * redesigned the structure of a plugin 50 | * `effectType` applies to all units 51 | * added possibility to sets an order of layout of units of the timer 52 | * added possibility to add new language 53 | * rename CSS classes by BEM methodology 54 | 55 | ### 1.1.0 (2016-07-30) 56 | 57 | ### Features 58 | 59 | * added time zone support 60 | * added support of the time transfer on summer/winter time 61 | * added methods support 62 | * added method of set value to option 63 | * added minified version of plugin 64 | 65 | ### 1.0.1 (2015-02-24) 66 | 67 | ### Features 68 | 69 | * added option for change effect of counting 70 | * added documentation 71 | * added examples 72 | 73 | ### 1.0.0 (2014-12-10) 74 | 75 | ### Features 76 | 77 | * first use timer on real web-site 78 | -------------------------------------------------------------------------------- /source/types.ts: -------------------------------------------------------------------------------- 1 | export interface SyoTimerInternalOptions { 2 | date: Date | number; 3 | /** 4 | * sets an order of layout of units of the timer: 5 | * days (d) of hours ('h'), minute ('m'), second ('s'). 6 | */ 7 | layout: string; 8 | /** 9 | * `true` - the timer is periodic. 10 | * If the date until which counts the timer is reached, 11 | * the next value date which will count down 12 | * the timer is incremented by the value `periodInterval` 13 | */ 14 | periodic: boolean; 15 | /** 16 | * the period of the timer in `periodUnit` (if `periodic` is set to `true`) 17 | */ 18 | periodInterval: number; 19 | /** 20 | * the unit of measurement period timer 21 | */ 22 | periodUnit: Unit; 23 | /** 24 | * `true` - show hours, minutes and seconds with leading zeros 25 | * (2 hours 5 minutes 4 seconds = 02:05:04) 26 | */ 27 | doubleNumbers: boolean; 28 | /** 29 | * The effect of changing the value of seconds 30 | */ 31 | effectType: SyoTimerEffectType; 32 | /** 33 | * localization of a countdown signatures (days, hours, minutes, seconds) 34 | */ 35 | lang: string; 36 | /** 37 | * text above the countdown (may be as html string) 38 | */ 39 | headTitle: string; 40 | /** 41 | * text under the countdown (may be as html string) 42 | */ 43 | footTitle: string; 44 | afterDeadline(timerBlock: SyoTimerTimerBlock): void; 45 | itemTypes: UnitLong[]; 46 | itemsHas: ItemsHas; 47 | } 48 | 49 | export type SyoTimerOptions = Partial>; 50 | 51 | export type SyoTimerOptionProps = Exclude; 52 | export type SyoTimerOptionValues = Required[SyoTimerOptionProps]; 53 | 54 | export interface SyoTimerTimerBlock { 55 | headBlock: JQuery; 56 | bodyBlock: JQuery; 57 | footBlock: JQuery; 58 | } 59 | 60 | export type SyoTimerItemBlocks = { 61 | [key in UnitLong]?: JQuery; 62 | }; 63 | 64 | export type SyoTimerEffectType = 'none' | 'opacity'; 65 | 66 | export type SyoTimerMethods = 'setOption'; 67 | 68 | export type UnitLong = 'day' | 'hour' | 'minute' | 'second'; 69 | export type UnitShort = 'd' | 'h' | 'm' | 's'; 70 | 71 | export type Unit = UnitShort | UnitLong; 72 | 73 | export interface LinkedList { 74 | list: T[]; 75 | next(current: T): T | null; 76 | prev(current: T): T | null; 77 | } 78 | 79 | export type ItemsHas = Record; 80 | 81 | export interface LanguageHandler { 82 | (n: number, words: string[]): string; 83 | } 84 | 85 | type LanguageConfigBase = Record; 86 | 87 | interface LanguageConfig extends LanguageConfigBase { 88 | handler?: LanguageHandler; 89 | } 90 | 91 | export type SyoTimerLocalization = Record; 92 | 93 | export interface SyoTimerLang extends JQueryStatic { 94 | syotimerLang: SyoTimerLocalization; 95 | } 96 | -------------------------------------------------------------------------------- /resources/default.css: -------------------------------------------------------------------------------- 1 | .syotimer { 2 | font-family: sans-serif; 3 | text-align: center; 4 | 5 | margin: 30px auto 0; 6 | padding: 0 0 10px; 7 | } 8 | .syotimer-cell { 9 | display: inline-block; 10 | margin: 0 5px; 11 | 12 | width: 79px; 13 | background: url("data:image/png;base64,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") 14 | no-repeat 0 0; 15 | } 16 | .syotimer-cell__value { 17 | font-size: 35px; 18 | color: #80a3ca; 19 | 20 | height: 81px; 21 | line-height: 81px; 22 | 23 | margin: 0 0 5px; 24 | } 25 | .syotimer-cell__unit { 26 | font-size: 12px; 27 | text-transform: uppercase; 28 | } 29 | -------------------------------------------------------------------------------- /source/utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DAY, 3 | HOUR, 4 | MINUTE, 5 | SECOND, 6 | DAY_IN_SEC, 7 | HOUR_IN_SEC, 8 | MINUTE_IN_SEC, 9 | LAYOUT_TYPES, 10 | unitLinkedList, 11 | } from './constants'; 12 | import type { LanguageHandler, SyoTimerInternalOptions, Unit, UnitLong, UnitShort } from './types'; 13 | 14 | /** 15 | * Determine a unit of period in milliseconds 16 | */ 17 | function getPeriodUnit(periodUnit: Unit) { 18 | switch (periodUnit) { 19 | case 'd': 20 | case DAY: 21 | return DAY_IN_SEC; 22 | case 'h': 23 | case HOUR: 24 | return HOUR_IN_SEC; 25 | case 'm': 26 | case MINUTE: 27 | return MINUTE_IN_SEC; 28 | case 's': 29 | case SECOND: 30 | default: 31 | return 1; 32 | } 33 | } 34 | 35 | /** 36 | * Formation of numbers with leading zeros 37 | */ 38 | export function format2(numb: number, isUse?: boolean) { 39 | return numb <= 9 && !!isUse ? `0${numb}` : String(numb); 40 | } 41 | 42 | export function getItemTypesByLayout(layout: string) { 43 | const itemTypes = [] as UnitLong[]; 44 | for (let i = 0; i < layout.length; i += 1) { 45 | itemTypes.push(LAYOUT_TYPES[layout[i] as UnitShort]); 46 | } 47 | return itemTypes; 48 | } 49 | 50 | /** 51 | * Getting count of units to deadline 52 | */ 53 | export function getUnitsToDeadLine(secondsToDeadLine: number) { 54 | let remainsSeconds = secondsToDeadLine; 55 | let unit: UnitLong | null = DAY; 56 | const unitsToDeadLine: Record = { 57 | day: 0, 58 | hour: 0, 59 | minute: 0, 60 | second: 0, 61 | }; 62 | do { 63 | const unitInMilliSec = getPeriodUnit(unit); 64 | unitsToDeadLine[unit] = Math.floor(remainsSeconds / unitInMilliSec); 65 | remainsSeconds %= unitInMilliSec; 66 | // eslint-disable-next-line no-cond-assign 67 | } while ((unit = unitLinkedList.prev(unit))); 68 | return unitsToDeadLine; 69 | } 70 | 71 | /** 72 | * Return once cell DOM of countdown: day, hour, minute, second 73 | */ 74 | export function getTimerItem() { 75 | const timerCellValue = $('
', { 76 | class: 'syotimer-cell__value', 77 | text: '0', 78 | }); 79 | const timerCellUnit = $('
', { class: 'syotimer-cell__unit' }); 80 | const timerCell = $('
', { class: 'syotimer-cell' }); 81 | timerCell.append(timerCellValue).append(timerCellUnit); 82 | return timerCell; 83 | } 84 | 85 | /** 86 | * Getting count of seconds to deadline 87 | */ 88 | export function getSecondsToDeadLine( 89 | differenceInMilliSec: number, 90 | options: SyoTimerInternalOptions, 91 | ) { 92 | let differenceInSeconds = differenceInMilliSec / 1000; 93 | differenceInSeconds = Math.floor(differenceInSeconds); 94 | 95 | if (!options.periodic) return differenceInSeconds; 96 | 97 | let differenceInUnit: number; 98 | const periodUnitInSeconds = getPeriodUnit(options.periodUnit); 99 | let fullTimeUnitsBetween = differenceInMilliSec / (periodUnitInSeconds * 1000); 100 | fullTimeUnitsBetween = Math.ceil(fullTimeUnitsBetween); 101 | fullTimeUnitsBetween = Math.abs(fullTimeUnitsBetween); 102 | if (differenceInSeconds >= 0) { 103 | differenceInUnit = fullTimeUnitsBetween % options.periodInterval; 104 | differenceInUnit = differenceInUnit === 0 ? options.periodInterval : differenceInUnit; 105 | differenceInUnit -= 1; 106 | } else { 107 | differenceInUnit = options.periodInterval - (fullTimeUnitsBetween % options.periodInterval); 108 | } 109 | 110 | const additionalInUnit = differenceInSeconds % periodUnitInSeconds; 111 | // fix когда дедлайн раньше текущей даты, 112 | // возникает баг с неправильным расчетом интервала при different пропорциональной periodUnit 113 | if (additionalInUnit === 0 && differenceInSeconds < 0) { 114 | differenceInUnit -= 1; 115 | } 116 | const secondsToDeadLine = Math.abs(differenceInUnit * periodUnitInSeconds + additionalInUnit); 117 | return secondsToDeadLine; 118 | } 119 | 120 | /** 121 | * Universal function for get correct inducement of nouns after a numeral (`number`) 122 | */ 123 | const universal: LanguageHandler = (n: number, words: string[]) => (n === 1 ? words[0] : words[1]); 124 | 125 | /** 126 | * Getting the correct declension of words after numerals 127 | */ 128 | export function getNumeral(n: number, lang: string, unit: UnitLong) { 129 | const handler: LanguageHandler = $.syotimerLang[lang].handler || universal; 130 | const words: string[] = $.syotimerLang[lang][unit]; 131 | return handler(n, words); 132 | } 133 | -------------------------------------------------------------------------------- /assets/js/syotimer.examples.js: -------------------------------------------------------------------------------- 1 | jQuery(function ($) { 2 | /* Simple Timer. The countdown to 20:30 2035.05.09 */ 3 | $('#simple-timer').syotimer({ 4 | date: new Date(2035, 4, 9, 20, 30), 5 | }); 6 | 7 | /* Timer with Head and Foot. Countdown is over */ 8 | $('#expired-timer').syotimer({ 9 | date: new Date(1990, 0), 10 | headTitle: '

Timer with header and footer. Countdown is over

', 11 | footTitle: 'Footer of timer.', 12 | }); 13 | 14 | /* Callback after the end of the countdown timer */ 15 | $('#expired-timer_event').syotimer({ 16 | date: new Date(2000, 11, 31), 17 | headTitle: '

Timer with header and footer. Countdown is over

', 18 | footTitle: 'Footer of timer.', 19 | afterDeadline: function (syotimer) { 20 | syotimer.bodyBlock.html('

The countdown is finished 00:00 2000.12.31

'); 21 | syotimer.headBlock.html('

Callback after the end of the countdown timer

'); 22 | syotimer.footBlock.html( 23 | '' + 'Footer of timer after countdown is finished' + '', 24 | ); 25 | }, 26 | }); 27 | 28 | /* Periodic Timer. Period is equal 3 minutes. Effect of fading in */ 29 | $('#periodic-timer_period_minutes').syotimer({ 30 | date: new Date(2015, 0, 1), 31 | layout: 'hms', 32 | doubleNumbers: false, 33 | effectType: 'opacity', 34 | 35 | periodUnit: 'm', 36 | periodic: true, 37 | periodInterval: 3, 38 | }); 39 | 40 | /* Periodic Timer. Period is equal 10 days */ 41 | $('#periodic-timer_period_days').syotimer({ 42 | date: new Date(2015, 0, 1, 20), 43 | layout: 'hms', 44 | periodic: true, 45 | periodInterval: 10, 46 | periodUnit: 'd', 47 | }); 48 | 49 | /* Demonstrate layout. Period is equal 2 hours. Display only seconds */ 50 | $('#layout-timer_only-seconds').syotimer({ 51 | layout: 's', 52 | periodic: true, 53 | periodInterval: 2, 54 | periodUnit: 'h', 55 | }); 56 | 57 | /* Demonstrate layout. Period is equal 2 days and 5 hours. 58 | Units of countdown in reverse order */ 59 | $('#layout-timer_reversed-units').syotimer({ 60 | layout: 'smhd', 61 | effectType: 'opacity', 62 | 63 | periodic: true, 64 | periodInterval: 53, 65 | periodUnit: 'h', 66 | }); 67 | 68 | /* Demonstrate layout. Period is equal 2 days and 5 hours. 69 | Display only days and minutes in reverse order */ 70 | $('#layout-timer_mixed-units').syotimer({ 71 | layout: 'md', 72 | 73 | periodic: true, 74 | periodInterval: 1777, 75 | periodUnit: 'm', 76 | }); 77 | 78 | /* Periodic Timer. Countdown timer with given time zone */ 79 | $('#periodic-timer_timezone_given').syotimer({ 80 | date: new Date('2000-07-01T18:00:00.000+02:00'), 81 | layout: 'hms', 82 | 83 | periodic: true, 84 | periodInterval: 1, 85 | periodUnit: 'd', 86 | }); 87 | 88 | /** 89 | * Periodic Timer. 90 | * Change options: doubleNumbers, effect type, language 91 | */ 92 | var changeOptionsTimer = $('#periodic-timer_change-options'); 93 | var effectTypeEl = $('#select-effect'); 94 | var formatNumberEl = $('#select-format-number'); 95 | var languageEl = $('#select-language'); 96 | 97 | changeOptionsTimer.syotimer({ 98 | periodic: true, 99 | periodInterval: 10, 100 | periodUnit: 'd', 101 | }); 102 | 103 | effectTypeEl.on('change', function () { 104 | var effectType = $('option:selected', this).val(); 105 | changeOptionsTimer.syotimer('setOption', 'effectType', effectType); 106 | }); 107 | 108 | formatNumberEl.on('change', function () { 109 | var formatNumberValue = $('option:selected', this).val(); 110 | var doubleNumbers = formatNumberValue === 'true'; 111 | changeOptionsTimer.syotimer('setOption', 'doubleNumbers', doubleNumbers); 112 | }); 113 | 114 | languageEl.on('change', function () { 115 | var language = $('option:selected', this).val(); 116 | changeOptionsTimer.syotimer('setOption', 'lang', language); 117 | }); 118 | 119 | /** 120 | * Localization in timer. 121 | * Add new language 122 | */ 123 | 124 | // Adding of a words for signatures of countdown 125 | $.syotimerLang.neng = { 126 | second: ['secondone', 'secondfive', 'seconds'], 127 | minute: ['minuteone', 'minutefive', 'minutes'], 128 | hour: ['hourone', 'hourfive', 'hours'], 129 | day: ['dayone', 'dayfive', 'days'], 130 | // Adding of the handler that selects an index from the list of words 131 | // based on ahead the going number 132 | handler: function nengNumeral(number, words) { 133 | var lastDigit = number % 10; 134 | var index = 2; 135 | if (lastDigit === 1) { 136 | index = 0; 137 | } else if (lastDigit === 5) { 138 | index = 1; 139 | } 140 | return words[index]; 141 | }, 142 | }; 143 | 144 | $('#periodic-timer_localization_new-english').syotimer({ 145 | lang: 'neng', 146 | layout: 'ms', 147 | 148 | periodic: true, 149 | periodInterval: 6, 150 | periodUnit: 'm', 151 | }); 152 | }); 153 | -------------------------------------------------------------------------------- /source/SyoTimer.ts: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import { defaultOptions, defaultItemsHas, DAY, SECOND, unitLinkedList } from './constants'; 3 | import { 4 | getItemTypesByLayout, 5 | getNumeral, 6 | getSecondsToDeadLine, 7 | getTimerItem, 8 | getUnitsToDeadLine, 9 | format2, 10 | } from './utils'; 11 | import type { 12 | SyoTimerOptions, 13 | SyoTimerInternalOptions, 14 | SyoTimerItemBlocks, 15 | SyoTimerEffectType, 16 | UnitLong, 17 | } from './types'; 18 | 19 | export class SyoTimer { 20 | element: JQuery; 21 | 22 | constructor(element: HTMLElement, options: SyoTimerInternalOptions) { 23 | this.element = $(element); 24 | this.element.data('syotimer-options', options); 25 | this.render(); 26 | } 27 | 28 | /** 29 | * Rendering base elements of countdown 30 | * @private 31 | */ 32 | private render() { 33 | const options = this.element.data('syotimer-options') as SyoTimerInternalOptions; 34 | 35 | const timerItem = getTimerItem(); 36 | const headBlock = $('
', { class: 'syotimer__head' }).html(options.headTitle); 37 | const bodyBlock = $('
', { class: 'syotimer__body' }); 38 | const footBlock = $('
', { class: 'syotimer__footer' }).html(options.footTitle); 39 | const itemBlocks: SyoTimerItemBlocks = {}; 40 | 41 | for (let i = 0; i < options.itemTypes.length; i += 1) { 42 | const item = timerItem.clone(); 43 | 44 | item.addClass(`syotimer-cell_type_${options.itemTypes[i]}`); 45 | bodyBlock.append(item); 46 | 47 | itemBlocks[options.itemTypes[i]] = item; 48 | } 49 | 50 | const timerBlocks = { headBlock, bodyBlock, footBlock }; 51 | 52 | this.element 53 | .data('syotimer-blocks', timerBlocks) 54 | .data('syotimer-items', itemBlocks) 55 | .addClass('syotimer') 56 | .append(headBlock) 57 | .append(bodyBlock) 58 | .append(footBlock); 59 | } 60 | 61 | /** 62 | * Handler called per seconds while countdown is not over 63 | */ 64 | tick() { 65 | const options = this.element.data('syotimer-options') as SyoTimerInternalOptions; 66 | $('.syotimer-cell > .syotimer-cell__value', this.element).css('opacity', 1); 67 | const currentTime = new Date().getTime(); 68 | const deadLineTime = options.date instanceof Date ? options.date.getTime() : options.date; 69 | const differenceInMilliSec = deadLineTime - currentTime; 70 | const secondsToDeadLine = getSecondsToDeadLine(differenceInMilliSec, options); 71 | if (secondsToDeadLine >= 0) { 72 | this.refreshUnitsDom(secondsToDeadLine); 73 | this.applyEffectSwitch(options.effectType); 74 | } else { 75 | const elementBox = $.extend(this.element, this.element.data('syotimer-blocks')); 76 | options.afterDeadline(elementBox); 77 | } 78 | } 79 | 80 | /** 81 | * Refresh unit DOM of countdown 82 | * @private 83 | */ 84 | private refreshUnitsDom(secondsToDeadLine: number) { 85 | const options = this.element.data('syotimer-options') as SyoTimerInternalOptions; 86 | const itemBlocks = this.element.data('syotimer-items'); 87 | const unitList = options.itemTypes; 88 | const unitsToDeadLine = getUnitsToDeadLine(secondsToDeadLine); 89 | 90 | if (!options.itemsHas.day) { 91 | unitsToDeadLine.hour += unitsToDeadLine.day * 24; 92 | } 93 | if (!options.itemsHas.hour) { 94 | unitsToDeadLine.minute += unitsToDeadLine.hour * 60; 95 | } 96 | if (!options.itemsHas.minute) { 97 | unitsToDeadLine.second += unitsToDeadLine.minute * 60; 98 | } 99 | for (let i = 0; i < unitList.length; i += 1) { 100 | const unit = unitList[i]; 101 | const unitValue = unitsToDeadLine[unit]; 102 | const itemBlock = itemBlocks[unit]; 103 | itemBlock.data('syotimer-unit-value', unitValue); 104 | $('.syotimer-cell__value', itemBlock).html( 105 | format2(unitValue, unit !== DAY ? options.doubleNumbers : false), 106 | ); 107 | $('.syotimer-cell__unit', itemBlock).html(getNumeral(unitValue, options.lang, unit)); 108 | } 109 | } 110 | 111 | /** 112 | * Applying effect of changing numbers 113 | * @private 114 | */ 115 | private applyEffectSwitch(effectType: SyoTimerEffectType, unit: UnitLong = SECOND) { 116 | switch (effectType) { 117 | case 'opacity': { 118 | const itemBlocks = this.element.data('syotimer-items'); 119 | const unitItemBlock = itemBlocks[unit]; 120 | if (unitItemBlock) { 121 | const nextUnit = unitLinkedList.next(unit); 122 | const unitValue = unitItemBlock.data('syotimer-unit-value'); 123 | $('.syotimer-cell__value', unitItemBlock).animate({ opacity: 0.1 }, 1000, 'linear', () => 124 | this.tick(), 125 | ); 126 | if (nextUnit && unitValue === 0) { 127 | this.applyEffectSwitch(effectType, nextUnit); 128 | } 129 | } 130 | return; 131 | } 132 | case 'none': 133 | default: { 134 | setTimeout(() => this.tick(), 1000); 135 | } 136 | } 137 | } 138 | } 139 | 140 | export default function mapSyoTimer(elements: JQuery, inputOptions?: SyoTimerOptions) { 141 | const options = $.extend({}, defaultOptions, inputOptions || {}); 142 | options.itemTypes = getItemTypesByLayout(options.layout); 143 | options.itemsHas = $.extend({}, defaultItemsHas); 144 | 145 | for (let i = 0; i < options.itemTypes.length; i += 1) { 146 | options.itemsHas[options.itemTypes[i]] = true; 147 | } 148 | 149 | return elements.each(function init() { 150 | const timer = new SyoTimer(this, options); 151 | timer.tick(); 152 | }); 153 | } 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQuery SyoTimer Plugin 2 | 3 | jQuery plugin of countdown on html page 4 | 5 | ## Demo 6 | 7 | [Examples of usage jQuery SyoTimer Plugin](https://mrfratello.github.io/SyoTimer) 8 | 9 | ## Features 10 | 11 | - Periodic counting with the specified period 12 | - Effects of change of indications of the countdown 13 | - The correct declension of nouns next to numeral numerals 14 | - An opportunity to add the language of countdown signatures which isn't included in the standard version of the plugin 15 | - Callback after the end of the countdown timer with the possibility of changing the structure of the timer 16 | - Custom formatting and styling timer 17 | 18 | ## Installing 19 | 20 | In a browser. Need download the [latest release](https://github.com/mrfratello/syotimer/releases/latest). And include the JavaScript file which you can find in the `build` folder: 21 | 22 | ```html 23 | 24 | 25 | ``` 26 | 27 | Using npm: 28 | 29 | ``` 30 | $ npm install jquery-syotimer 31 | ``` 32 | 33 | Using yarn: 34 | 35 | ``` 36 | $ yarn add jquery-syotimer 37 | ``` 38 | 39 | ## Usage 40 | 41 | Syotimer plugin can be integrated with plain JavaScript or with different module loaders. 42 | 43 | Script Tag: 44 | 45 | ```html 46 | 51 | ``` 52 | 53 | Common JS: 54 | 55 | ```javascript 56 | const $ = require("jquery"); 57 | require("jquery-syotimer"); 58 | 59 | $(".selector_to_countdown").syotimer(); 60 | ``` 61 | 62 | Bundlers (Webpack, etc): 63 | 64 | ```javascript 65 | import $ from "jquery"; 66 | import "jquery-syotimer"; 67 | 68 | $(".selector_to_countdown").syotimer(); 69 | ``` 70 | 71 | ## Markup 72 | 73 | Classes is named by [BEM methodology](https://en.bem.info/methodology/naming-convention/) 74 | 75 | ```html 76 |
77 |
78 |
79 |
80 |
1
81 |
day
82 |
83 |
84 |
1
85 |
hour
86 |
87 |
88 |
1
89 |
minute
90 |
91 |
92 |
1
93 |
second
94 |
95 |
96 | 97 |
98 | ``` 99 | 100 | Example of css styles for syotimer in [resources/default.css](resources/default.css). 101 | 102 | ## Options 103 | 104 | | Option | Description | Type of Value | Default Value | Available Values | 105 | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ------------- | --------------------------------- | 106 | | `date` | date, which should count down timer | integer/Date | 0 | | 107 | | `layout` | sets an order of layout of units of the timer: days (d) of hours ('h'), minute ('m'), second ('s'). | string | 'dhms' | | 108 | | `doubleNumbers` | `true` - show hours, minutes and seconds with leading zeros (2 hours 5 minutes 4 seconds = 02:05:04) | boolean | true | | 109 | | `effectType` | The effect of changing the value of seconds | string | 'none' | 'none', 'opacity' | 110 | | `lang` | localization of a countdown signatures (days, hours, minutes, seconds) | string | 'eng' | see [Localization](#localization) | 111 | | `periodic` | `true` - the timer is periodic. If the date until which counts the timer is reached, the next value date which will count down the timer is incremented by the value `periodInterval` | boolean | false | | 112 | | `periodInterval` | the period of the timer in `periodUnit` (if `periodic` is set to `true`) | integer | 7 | >0 | 113 | | `periodUnit` | the unit of measurement period timer | string | 'd' | 'd', 'h', 'm', 's' | 114 | 115 | ## Methods 116 | 117 | The use of the methods has the following syntax: 118 | 119 | ```javascript 120 | $('.your_selector_to_countdown').syotimer(nameOfMethod, param1, param2, ... , paramN); 121 | ``` 122 | 123 | ### setOption 124 | 125 | `setOption` - assigns a value to the option 126 | 127 | **Parameters:** 128 | 129 | 1. Name of option 130 | 1. Value 131 | 132 | **Code examples:** 133 | 134 | ```javascript 135 | $(".your_selector_to_countdown").syotimer("setOption", "effectType", "opacity"); 136 | ``` 137 | 138 | ## Localization 139 | 140 | ### Default languages 141 | 142 | By default the supported plugin languages: 143 | 144 | | Language | Value of `lang` option | 145 | | ---------- | ---------------------- | 146 | | English | 'eng' | 147 | | Russian | 'rus' | 148 | | Spanish | 'spa' | 149 | | Portuguese | 'por' | 150 | | Hebrew | 'heb' | 151 | 152 | ### Adding new language 153 | 154 | It is very simple to execute localization of a plugin under the language. 155 | You need to add the translations of signatures to timer elements as the parameter of an object of `$.syotimerLang`. 156 | Then you need determine a new language in the syotimer options. For example we will add support of Spanish 157 | (though this language is supported by default): 158 | 159 | ```javascript 160 | $.syotimerLang.spa = { 161 | seconds: ["segundo", "segundos"], 162 | minute: ["minuto", "minutos"], 163 | hour: ["hora", "horas"], 164 | day: ["dia", "dias"], 165 | }; 166 | 167 | $(".your_selector_to_countdown").syotimer({ 168 | lang: "spa", 169 | }); 170 | ``` 171 | 172 | ### Inducement of a noun after a numeral 173 | 174 | At the majority of languages a simple algorithm of determination of inducement of a noun after a numeral. 175 | If numeral is equal `1` then need input first element from array. Otherwise - second element. 176 | 177 | But there are languages in which more difficult rules of determination of the correct inducement of nouns after a numeral (for example, Russian). 178 | 179 | For example, consider a completely synthetic language (let it be called "Nenglish"). 180 | It is very similar to English but there are significant differences in the spelling of nouns after numerals. 181 | Namely, the difference in the suffixes of these nouns: 182 | 183 | - if the number ends with the digit `1` then to the noun added the suffix "one" (21 secondone, 1 minuteone, ...); 184 | - if the number ends with the digit `5` then the suffix is equal "five" (35 hourfive, 5 secondfive); 185 | - otherwise the suffix is equal to "s" (24 minutes, 3 days). 186 | 187 | To add a Nenglish in Syotimer need first add all possible variants of a writing of the captions of the items of the plugin. 188 | The abbreviated name of the language will take "neng": 189 | 190 | ```javascript 191 | $.syotimerLang.neng = { 192 | second: ["secondone", "secondfive", "seconds"], 193 | minute: ["minuteone", "minutefive", "minutes"], 194 | hour: ["hourone", "hourfive", "hours"], 195 | day: ["dayone", "dayfive", "days"], 196 | handler: function nengNumeral(number, words) { 197 | var lastDigit = number % 10; 198 | var index = 2; 199 | if (lastDigit === 1) { 200 | index = 0; 201 | } else if (lastDigit === 5) { 202 | index = 1; 203 | } 204 | return words[index]; 205 | }, 206 | }; 207 | ``` 208 | 209 | The "handler" must contain a function that receive the two arguments: a number and an array of nouns. 210 | This method should return the the noun. 211 | 212 | Then only have to specify the language when you create the instance Syotimer: 213 | 214 | ```javascript 215 | $(".your_selector_to_countdown").syotimer({ 216 | lang: "neng", 217 | }); 218 | ``` 219 | 220 | ## Requirements 221 | 222 | jQuery SyoTimer Plugin has been tested with jQuery 1.7+ on all major browsers: 223 | 224 | - Firefox 2+ (Win, Mac, Linux); 225 | - IE8+ (Win); 226 | - Chrome 6+ (Win, Mac, Linux, Android, iPhone); 227 | - Safari 3.2+ (Win, Mac, iPhone); 228 | - Opera 8+ (Win, Mac, Linux, Android, iPhone). 229 | 230 | Gratitude to [Yuri Danilchenko](https://github.com/yuri-danilchenko) and Elena Levin. 231 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Examples of usage jQuery SyoTimer Plugin 7 | 98 | 99 | 100 | 101 |

Examples of usage jQuery SyoTimer Plugin

102 | 103 |
104 |
105 |

Simple Timer

106 |
107 |
108 |
/* Simple Timer. The countdown to 20:30 2035.05.09 */
111 | $("#simple-timer").syotimer({
112 |   date: new Date(2035, 4, 9, 20, 30),
113 | });
114 | 
115 |
116 | 117 |
118 |
119 |
/* Timer with Head and Foot. Countdown is over */
122 | $("#expired-timer").syotimer({
123 |   date: new Date(1990, 0),
124 |   headTitle: "<h3>Timer with header and footer. Countdown is over</h3>",
125 |   footTitle: '<i style="color: brown;">Footer of timer.</i>',
126 | });
127 | 
128 |
129 | 130 |
131 |
132 |
/* Callback after the end of the countdown timer */
135 | $("#expired-timer_event").syotimer({
136 |   date: new Date(2000, 11, 31),
137 |   headTitle: "<h3>Timer with header and footer. Countdown is over</h3>",
138 |   footTitle: '<i style="color: brown;">Footer of timer.</i>',
139 |   afterDeadline: function (syotimer) {
140 |     syotimer.bodyBlock.html(
141 |       "<p>The countdown is finished 00:00 2000.12.31</p>"
142 |     );
143 |     syotimer.headBlock.html(
144 |       "<h3>Callback after the end of the countdown timer</h3>"
145 |     );
146 |     syotimer.footBlock.html(
147 |       '<em style="color:brown;">' +
148 |         "Footer of timer after countdown is finished" +
149 |         "</em>"
150 |     );
151 |   },
152 | });
153 | 
154 |
155 | 156 |
157 |

158 | Periodic Timer. The countdown begins first through each 3 minutes. Effect of fading in 159 |

160 |
161 |
/* Periodic Timer. Period is equal 3 minutes. Effect of fading in */
164 | $("#periodic-timer_period_minutes").syotimer({
165 |   date: new Date(2015, 0, 1),
166 |   layout: "hms",
167 |   doubleNumbers: false,
168 |   effectType: "opacity",
169 | 
170 |   periodUnit: "m",
171 |   periodic: true,
172 |   periodInterval: 3,
173 | });
174 | 
175 |
176 | 177 |
178 |

179 | Periodic Timer. The countdown begins first through each 10 days 180 |

181 |

The date equal 20:00 2015.01.01

182 |
183 | 184 |
/* Periodic Timer. Period is equal 10 days */
187 | $("#periodic-timer_period_days").syotimer({
188 |   date: new Date(2015, 0, 1, 20),
189 |   layout: "hms",
190 |   periodic: true,
191 |   periodInterval: 10,
192 |   periodUnit: "d",
193 | });
194 | 
195 |
196 | 197 |
198 |

Display only seconds

199 |

Demonstrate layout. Period is equal 2 hours.

200 |
201 | 202 |
/* Demonstrate layout. Period is equal 2 hours. Display only seconds */
205 | $("#layout-timer_only-seconds").syotimer({
206 |   layout: "s",
207 |   periodic: true,
208 |   periodInterval: 2,
209 |   periodUnit: "h",
210 | });
211 | 
212 |
213 | 214 |
215 |

Units of countdown in reverse order

216 |

Demonstrate layout. Period is equal 2 days and 5 hours.

217 |
218 | 219 |
/* Demonstrate layout. Period is equal 2 days and 5 hours.
222 |     Units of countdown in reverse order */
223 | $("#layout-timer_reversed-units").syotimer({
224 |   layout: "smhd",
225 |   effectType: "opacity",
226 | 
227 |   periodic: true,
228 |   periodInterval: 53,
229 |   periodUnit: "h",
230 | });
231 | 
232 |
233 | 234 |
235 |

Display only days and minutes in reverse order

236 |

237 | Demonstrate layout. Period is equal 1 days, 5 hours and 37 minutes. 238 |

239 |
240 | 241 |
/* Demonstrate layout. Period is equal 2 days and 5 hours.
244 |   Display only days and minutes in reverse order */
245 | $("#layout-timer_mixed-units").syotimer({
246 |   layout: "md",
247 | 
248 |   periodic: true,
249 |   periodInterval: 1777,
250 |   periodUnit: "m",
251 | });
252 | 
253 |
254 | 255 |
256 |

Countdown timer with given timezone

257 |

The deadline is 18:00:00 by the time Rome, Italy (UTC+2)

258 |
259 | 260 |
/* Periodic Timer. Countdown timer with given time zone */
263 | $("#periodic-timer_timezone_given").syotimer({
264 |   date: new Date("2000-07-01T18:00:00.000+02:00"),
265 |   layout: "hms",
266 | 
267 |   periodic: true,
268 |   periodInterval: 1,
269 |   periodUnit: "d",
270 | });
271 | 
272 |
273 | 274 |
275 |
276 |

Change options of countdown

277 |
278 | 279 | 283 | 284 | 285 | 289 | 290 | 291 | 296 |
297 |
298 |
299 | 300 |
/**
304 |  * Periodic Timer.
305 |  * Change options: doubleNumbers, effect type, language
306 |  */
307 | var changeOptionsTimer = $('#periodic-timer_change-options');
308 | var effectTypeEl = $('#select-effect');
309 | var formatNumberEl = $('#select-format-number');
310 | var languageEl = $('#select-language');
311 |  
312 | changeOptionsTimer.syotimer({
313 |   periodic: true,
314 |   periodInterval: 10,
315 |   periodUnit: 'd',
316 | });
317 |  
318 | effectTypeEl.on('change', function () {
319 |   var effectType = $('option:selected', this).val();
320 |   changeOptionsTimer.syotimer('setOption', 'effectType', effectType);
321 | });
322 |  
323 | formatNumberEl.on('change', function () {
324 |   var formatNumberValue = $('option:selected', this).val();
325 |   var doubleNumbers = formatNumberValue === 'true';
326 |   changeOptionsTimer.syotimer('setOption', 'doubleNumbers', doubleNumbers);
327 | });
328 |  
329 | languageEl.on('change', function () {
330 |   var language = $('option:selected', this).val();
331 |   changeOptionsTimer.syotimer('setOption', 'lang', language);
332 | });
333 |
334 | 335 |
336 |

Adding new language

337 |

Demonstrate adding the new language of signatures.

338 |
339 |
340 |

341 | Nenglish is a synthetic language. It is very similar to English but there are significant 342 | differences in the spelling of nouns after numerals. Namely, the difference in the 343 | suffixes of these nouns: 344 |

345 |
    346 |
  • 347 | if the number ends with the digit 1 then to the noun added the suffix "one" 348 | (21 secondone, 1 minuteone, ...); 349 |
  • 350 |
  • 351 | if the number ends with the digit 5 then the suffix is equal "five" (35 352 | hourfive, 5 secondfive); 353 |
  • 354 |
  • otherwise the suffix is equal to "s" (24 minutes, 3 days).
  • 355 |
356 |
357 | 358 |
/**
359 |  * Localization in timer.
360 |  * Add new language
361 |  */
362 | 
363 | // Adding of a words for signatures of countdown
364 | $.syotimerLang.neng = {
365 |   second: ["secondone", "secondfive", "seconds"],
366 |   minute: ["minuteone", "minutefive", "minutes"],
367 |   hour: ["hourone", "hourfive", "hours"],
368 |   day: ["dayone", "dayfive", "days"],
369 |   // Adding of the handler that selects an index from the list of words
370 |   // based on ahead the going number
371 |   handler: function nengNumeral(number, words) {
372 |     var lastDigit = number % 10;
373 |     var index = 2;
374 |     if (lastDigit === 1) {
375 |       index = 0;
376 |     } else if (lastDigit === 5) {
377 |       index = 1;
378 |     }
379 |     return words[index];
380 |   },
381 | };
382 | 
383 | $("#periodic-timer_localization_new-english").syotimer({
384 |   lang: "neng",
385 |   layout: "ms",
386 | 
387 |   periodic: true,
388 |   periodInterval: 6,
389 |   periodUnit: "m",
390 | });
391 | 
392 |
393 | 394 |
395 |
396 |
397 | SyoTimer on GitHub 398 | 399 | Fork me on GitHub 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | --------------------------------------------------------------------------------