From 97d9a9eb750e9231c11bb46fed4081e59c01820b Mon Sep 17 00:00:00 2001 From: Gidon Date: Tue, 18 Apr 2017 17:03:37 +0300 Subject: [PATCH 01/11] Initial setup --- scripts/rateit.ts | 163 ++++++++++++++++++++++++++++++++++++++++++ scripts/tsconfig.json | 14 ++++ 2 files changed, 177 insertions(+) create mode 100644 scripts/rateit.ts create mode 100644 scripts/tsconfig.json diff --git a/scripts/rateit.ts b/scripts/rateit.ts new file mode 100644 index 0000000..912e149 --- /dev/null +++ b/scripts/rateit.ts @@ -0,0 +1,163 @@ +export type RateItMode = "bg" | "font"; + +export interface RateItOptions { + value?: number; + min?: number; + max?: number; + step?: number; + backingfld?: string; + readonly?: boolean; + ispreset?: boolean; + resetable?: boolean; + starwidth?: number; + starheight?: number; + mode?: RateItMode; + icon?: string; +} + +type RateItItemDataTypes = "init" | "value" | "min" | "max" | "step" | "backingfld" | "readonly" | "ispreset" | "resetable" | "starwidth" | "starheight"| "mode"|"icon"; +export class rateit { + + private index : number = 0; + + private defaultOptions: RateItOptions = { + min: 0, + max: 5, + step: 0.5, + mode: 'bg', + icon: '★', + starwidth: 16, + starheight: 16, + readonly: false, + resetable: false, + ispreset: false + }; + + + public enable(): void + public enable(selector: string): void + public enable(selector: string, options: RateItOptions): void + public enable(selector?: string, options?: RateItOptions): void { + options = options || this.defaultOptions; + selector = selector || '.rateit'; + + let items = document.querySelectorAll(selector); + + + + + for (var i = 0; i < items.length; i++) { + let item = items[i]; + let getItemNumber = (key: RateItItemDataTypes): number => Number(item.dataset[key]); + let getItemBoolean = (key: RateItItemDataTypes): boolean => Boolean(item.dataset[key]); + let getItemString = (key: RateItItemDataTypes): string => item.dataset[key]; + let setItem = (key: RateItItemDataTypes, value: any) => item.dataset[key] = value.toString(); + item.classList.add('rateit'); + + let ltr = item.style.direction === 'ltr'; + + setItem('mode', getItemNumber('mode') || options.mode) + setItem('icon', getItemNumber('icon') || options.icon) + setItem('min', isNaN(getItemNumber('min')) ? options.min : getItemNumber('min')) + setItem('max', isNaN(getItemNumber('max')) ? options.max : getItemNumber('max')) + setItem('step', item.dataset['step'] || options.step) + setItem('resetable', item.dataset['resetable'] !== undefined ? item.dataset['resetable'] : options.resetable) + setItem('readonly', item.dataset['readonly'] !== undefined ? item.dataset['readonly'] : options.readonly) + setItem('ispreset', item.dataset['ispreset'] !== undefined ? item.dataset['ispreset'] : options.ispreset) + setItem('backingfld', item.dataset['backingfld'] || options.backingfld) + setItem('starwidth', getItemNumber('starwidth') || options.starwidth) + setItem('starheight', getItemNumber('starheight') || options.starheight) + setItem('value', Math.max(getItemNumber('min'), Math.min(getItemNumber('max'), (!isNaN(getItemNumber('value')) ? getItemNumber('value') : options.value == null ? options.min : options.value)))) + + if (getItemString('backingfld')) { + //if we have a backing field, hide it, override defaults if range or select. + const el = document.querySelector(getItemString('backingfld')); + const fld = el; + fld.style.display = 'none'; + if (fld.disabled || fld.readOnly) { + setItem('readonly', true); + } + + if (el.nodeName === 'INPUT') { + if (fld.type === 'range' || fld.type === 'text') { //in browsers not support the range type, it defaults to text + + setItem('min', parseInt(fld.getAttribute('min')) || getItemNumber('min')); //if we would have done fld.min it wouldn't have worked in browsers not supporting the range type. + setItem('max', parseInt(fld.getAttribute('max')) || getItemNumber('max')); + setItem('step', parseInt(fld.getAttribute('step')) || getItemNumber('step')); + } + } + + if (el.nodeName == 'SELECT' && (el).options.length > 1) { + let select = el; + // If backing field is a select box with valuesrc option set to "index", use the indexes of its options; otherwise, use the values. + if (select.getAttribute('data-rateit-valuesrc') === 'index') { + setItem('min', (!isNaN(getItemNumber('min')) ? getItemNumber('min') : 0)); + setItem('max', select.options[select.length - 1].index); + setItem('step', 1); + } + else { + setItem('min', (!isNaN(getItemNumber('min')) ? getItemNumber('min') : 0)); + setItem('max', Number(select.options[select.length - 1].value)); + setItem('step', Number(select.options[1].value) - Number(select.options[0].value)); + } + //see if we have a option that as explicity been selected + var selectedOption = select.querySelector('option[selected]'); + if (selectedOption != null) { + // If backing field is a select box with valuesrc option set to "index", use the index of selected option; otherwise, use the value. + if (select.getAttribute('data-rateit-valuesrc') === 'index') { + setItem('value', selectedOption.index); + } + else { + setItem('value', selectedOption.value); + } + } + } + else { + //if it is not a select box, we can get's it's value using the val function. + //If it is a selectbox, we always get a value (the first one of the list), even if it was not explicity set. + setItem('value', fld.value); + } + } + + //Create the necessary tags. For ARIA purposes we need to give the items an ID. So we use an internal index to create unique ids + let element = item.nodeName === 'DIV' ? 'div' : 'span'; + this.index++; + + var html = `<${element} id="rateit-range-${this.index}" class="rateit-range" tabindex="0" role="slider" aria-label="' + $.rateit.aria.ratingLabel + '" aria-owns="rateit-reset-${this.index}" aria-valuemin="' + itemdata('min') + '" aria-valuemax="' + itemdata('max') + '" aria-valuenow="' + itemdata('value') + '"><${element} class="rateit-empty"><${element} class="rateit-selected"><${element} class="rateit-hover">`; + item.insertAdjacentHTML('beforeend', html); + + if (!ltr) { + (item.querySelector('.rateit-reset')).style.cssFloat = 'right'; + (item.querySelector('.rateit-selected')).classList.add('rateit-selected-rtl'); + (item.querySelector('.rateit-hover')).classList.add('rateit-hover-rtl'); + } + + if (getItemString('mode') == 'font') { + item.classList.add('rateit-font'); + item.classList.remove('rateit-bg'); + } + else { + item.classList.add('rateit-bg'); + item.classList.remove('rateit-font'); + } + + setItem('init', JSON.stringify(item.dataset));//cheap way to create a clone + } + + + } + + + public reset(selector: string): void { + + + } + + + +} + + + + + diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json new file mode 100644 index 0000000..4e25f6f --- /dev/null +++ b/scripts/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "module": "commonjs", + "noImplicitAny": true, + "removeComments": true, + "preserveConstEnums": true, + "sourceMap": true + + }, + "compileOnSave": true, + "files": [ + "rateit.ts" + ] +} \ No newline at end of file From 78ef0270d78b0e71828fae3394a5644965140222 Mon Sep 17 00:00:00 2001 From: Gidon Date: Tue, 18 Apr 2017 22:50:06 +0300 Subject: [PATCH 02/11] Add typescript compilation to gulp. --- gulpfile.js | 14 ++++++++++---- package.json | 8 +++++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 9aa57d5..146397a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -8,11 +8,15 @@ var uglify = require('gulp-uglify'); var plumber = require('gulp-plumber'); var rename = require('gulp-rename'); var sourcemaps = require('gulp-sourcemaps'); +var typescript = require('gulp-tsc'); +gulp.task('compile', function(){ + gulp.src(['scripts/*.ts']) + .pipe(typescript()) + .pipe(gulp.dest('scripts/')) +}); - - -gulp.task('default', function () { - return gulp.src('scripts/jquery.rateit.js') +gulp.task('minify', function () { + return gulp.src('scripts/*rateit.js') .pipe(sourcemaps.init()) .pipe(plumber()) .pipe(uglify({ preserveComments: 'license' })) @@ -20,3 +24,5 @@ gulp.task('default', function () { .pipe(sourcemaps.write('')) .pipe(gulp.dest('scripts')) // save .min.js }); + +gulp.task('default', ['compile', 'minify']); \ No newline at end of file diff --git a/package.json b/package.json index c9ba78a..5890b5e 100644 --- a/package.json +++ b/package.json @@ -24,10 +24,12 @@ "jquery": ">=1.6" }, "devDependencies": { - "gulp": "~3.9.1", + "gulp": "^3.9.1", "gulp-plumber": "~1.1.0", - "gulp-uglify": "~2.0.0", "gulp-rename": "~1.2.2", - "gulp-sourcemaps": "~2.1.1" + "gulp-sourcemaps": "~2.1.1", + "gulp-tsc": "~1.3.2", + "gulp-uglify": "~2.0.0", + "typescript": "~2.2.2" } } From 13eb7a18b80e8dcaa6ce12827b8e6a7b77c85c90 Mon Sep 17 00:00:00 2001 From: Gidon Date: Tue, 18 Apr 2017 22:50:27 +0300 Subject: [PATCH 03/11] add datasetwrapper --- scripts/rateit.ts | 127 +++++++++++++++++++++++++++++++--------------- 1 file changed, 85 insertions(+), 42 deletions(-) diff --git a/scripts/rateit.ts b/scripts/rateit.ts index 912e149..6fe5ff6 100644 --- a/scripts/rateit.ts +++ b/scripts/rateit.ts @@ -15,11 +15,41 @@ export interface RateItOptions { icon?: string; } -type RateItItemDataTypes = "init" | "value" | "min" | "max" | "step" | "backingfld" | "readonly" | "ispreset" | "resetable" | "starwidth" | "starheight"| "mode"|"icon"; +export class DateSetWrapper { + _item: HTMLElement; + // constructor(item: HTMLElement) { + // this._item = item; + // } + + public getItemNumber = (key: RateItItemDataTypes): number => Number(this._item.dataset[key]); + public getItemBoolean = (key: RateItItemDataTypes): boolean => Boolean(this._item.dataset[key]); + public getItemString = (key: RateItItemDataTypes): string => this._item.dataset[key]; + public setItem = (key: RateItItemDataTypes, value: any) => this._item.dataset[key] = value.toString(); +} + +interface DataSetWrapper { + + getNumber(key: RateItItemDataTypes): number; + getBoolean(key: RateItItemDataTypes): boolean; + getString(key: RateItItemDataTypes): string; + set(key: RateItItemDataTypes, value: any): void; +} + +function getDataSetWrapper(item: HTMLElement): DataSetWrapper { + return { + + getNumber: (key: RateItItemDataTypes): number => Number(this._item.dataset[key]), + getBoolean: (key: RateItItemDataTypes): boolean => Boolean(this._item.dataset[key]), + getString: (key: RateItItemDataTypes): string => this._item.dataset[key], + set: (key: RateItItemDataTypes, value: any) => this._item.dataset[key] = value.toString() + }; +} + +type RateItItemDataTypes = "init" | "value" | "min" | "max" | "step" | "backingfld" | "readonly" | "ispreset" | "resetable" | "starwidth" | "starheight" | "mode" | "icon"; export class rateit { - private index : number = 0; - + private index: number = 0; + private defaultOptions: RateItOptions = { min: 0, max: 5, @@ -48,42 +78,41 @@ export class rateit { for (var i = 0; i < items.length; i++) { let item = items[i]; - let getItemNumber = (key: RateItItemDataTypes): number => Number(item.dataset[key]); - let getItemBoolean = (key: RateItItemDataTypes): boolean => Boolean(item.dataset[key]); - let getItemString = (key: RateItItemDataTypes): string => item.dataset[key]; - let setItem = (key: RateItItemDataTypes, value: any) => item.dataset[key] = value.toString(); + //let dataSet = new DataSetWrapper(item); + let dataset: DataSetWrapper = getDataSetWrapper(item); + item.classList.add('rateit'); let ltr = item.style.direction === 'ltr'; - setItem('mode', getItemNumber('mode') || options.mode) - setItem('icon', getItemNumber('icon') || options.icon) - setItem('min', isNaN(getItemNumber('min')) ? options.min : getItemNumber('min')) - setItem('max', isNaN(getItemNumber('max')) ? options.max : getItemNumber('max')) - setItem('step', item.dataset['step'] || options.step) - setItem('resetable', item.dataset['resetable'] !== undefined ? item.dataset['resetable'] : options.resetable) - setItem('readonly', item.dataset['readonly'] !== undefined ? item.dataset['readonly'] : options.readonly) - setItem('ispreset', item.dataset['ispreset'] !== undefined ? item.dataset['ispreset'] : options.ispreset) - setItem('backingfld', item.dataset['backingfld'] || options.backingfld) - setItem('starwidth', getItemNumber('starwidth') || options.starwidth) - setItem('starheight', getItemNumber('starheight') || options.starheight) - setItem('value', Math.max(getItemNumber('min'), Math.min(getItemNumber('max'), (!isNaN(getItemNumber('value')) ? getItemNumber('value') : options.value == null ? options.min : options.value)))) - - if (getItemString('backingfld')) { + dataset.set('mode', dataset.getNumber('mode') || options.mode) + dataset.set('icon', dataset.getNumber('icon') || options.icon) + dataset.set('min', isNaN(dataset.getNumber('min')) ? options.min : dataset.getNumber('min')) + dataset.set('max', isNaN(dataset.getNumber('max')) ? options.max : dataset.getNumber('max')) + dataset.set('step', item.dataset['step'] || options.step) + dataset.set('resetable', item.dataset['resetable'] !== undefined ? item.dataset['resetable'] : options.resetable) + dataset.set('readonly', item.dataset['readonly'] !== undefined ? item.dataset['readonly'] : options.readonly) + dataset.set('ispreset', item.dataset['ispreset'] !== undefined ? item.dataset['ispreset'] : options.ispreset) + dataset.set('backingfld', item.dataset['backingfld'] || options.backingfld) + dataset.set('starwidth', dataset.getNumber('starwidth') || options.starwidth) + dataset.set('starheight', dataset.getNumber('starheight') || options.starheight) + dataset.set('value', Math.max(dataset.getNumber('min'), Math.min(dataset.getNumber('max'), (!isNaN(dataset.getNumber('value')) ? dataset.getNumber('value') : options.value == null ? options.min : options.value)))) + + if (dataset.getString('backingfld')) { //if we have a backing field, hide it, override defaults if range or select. - const el = document.querySelector(getItemString('backingfld')); + const el = document.querySelector(dataset.getString('backingfld')); const fld = el; fld.style.display = 'none'; if (fld.disabled || fld.readOnly) { - setItem('readonly', true); + dataset.set('readonly', true); } if (el.nodeName === 'INPUT') { if (fld.type === 'range' || fld.type === 'text') { //in browsers not support the range type, it defaults to text - setItem('min', parseInt(fld.getAttribute('min')) || getItemNumber('min')); //if we would have done fld.min it wouldn't have worked in browsers not supporting the range type. - setItem('max', parseInt(fld.getAttribute('max')) || getItemNumber('max')); - setItem('step', parseInt(fld.getAttribute('step')) || getItemNumber('step')); + dataset.set('min', parseInt(fld.getAttribute('min')) || dataset.getNumber('min')); //if we would have done fld.min it wouldn't have worked in browsers not supporting the range type. + dataset.set('max', parseInt(fld.getAttribute('max')) || dataset.getNumber('max')); + dataset.set('step', parseInt(fld.getAttribute('step')) || dataset.getNumber('step')); } } @@ -91,31 +120,31 @@ export class rateit { let select = el; // If backing field is a select box with valuesrc option set to "index", use the indexes of its options; otherwise, use the values. if (select.getAttribute('data-rateit-valuesrc') === 'index') { - setItem('min', (!isNaN(getItemNumber('min')) ? getItemNumber('min') : 0)); - setItem('max', select.options[select.length - 1].index); - setItem('step', 1); + dataset.set('min', (!isNaN(dataset.getNumber('min')) ? dataset.getNumber('min') : 0)); + dataset.set('max', select.length - 1); + dataset.set('step', 1); } else { - setItem('min', (!isNaN(getItemNumber('min')) ? getItemNumber('min') : 0)); - setItem('max', Number(select.options[select.length - 1].value)); - setItem('step', Number(select.options[1].value) - Number(select.options[0].value)); + dataset.set('min', (!isNaN(dataset.getNumber('min')) ? dataset.getNumber('min') : 0)); + dataset.set('max', Number(select.options[select.length - 1].value)); + dataset.set('step', Number(select.options[1].value) - Number(select.options[0].value)); } //see if we have a option that as explicity been selected - var selectedOption = select.querySelector('option[selected]'); + var selectedOption = select.querySelector('option[selected]'); if (selectedOption != null) { // If backing field is a select box with valuesrc option set to "index", use the index of selected option; otherwise, use the value. if (select.getAttribute('data-rateit-valuesrc') === 'index') { - setItem('value', selectedOption.index); + dataset.set('value', selectedOption.index); } else { - setItem('value', selectedOption.value); + dataset.set('value', selectedOption.value); } } } else { //if it is not a select box, we can get's it's value using the val function. //If it is a selectbox, we always get a value (the first one of the list), even if it was not explicity set. - setItem('value', fld.value); + dataset.set('value', fld.value); } } @@ -127,12 +156,12 @@ export class rateit { item.insertAdjacentHTML('beforeend', html); if (!ltr) { - (item.querySelector('.rateit-reset')).style.cssFloat = 'right'; + (item.querySelector('.rateit-reset')).style.cssFloat = 'right'; (item.querySelector('.rateit-selected')).classList.add('rateit-selected-rtl'); (item.querySelector('.rateit-hover')).classList.add('rateit-hover-rtl'); } - if (getItemString('mode') == 'font') { + if (dataset.getString('mode') == 'font') { item.classList.add('rateit-font'); item.classList.remove('rateit-bg'); } @@ -141,16 +170,32 @@ export class rateit { item.classList.remove('rateit-font'); } - setItem('init', JSON.stringify(item.dataset));//cheap way to create a clone + dataset.set('init', JSON.stringify(item.dataset));//cheap way to create a clone + + this.redraw(item); } + } + private redraw(item: HTMLElement) { + var dataset = getDataSetWrapper(item); + + } - public reset(selector: string): void { + public reset(element: HTMLElement): void + public reset(selector: string): void + public reset(selectorOrElement: any): void { + let element: HTMLElement; + if (typeof (selectorOrElement) === 'string') { + element = document.querySelector(selectorOrElement); + } else { + element = selectorOrElement; + } + } @@ -159,5 +204,3 @@ export class rateit { - - From 40ac2ae14b0c91705ea10bc572534da76313d58d Mon Sep 17 00:00:00 2001 From: Gidon Date: Tue, 18 Apr 2017 23:16:14 +0300 Subject: [PATCH 04/11] start of redraw method --- scripts/rateit.ts | 46 ++++++++++++++++++++++++------------------- scripts/tsconfig.json | 1 + 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/scripts/rateit.ts b/scripts/rateit.ts index 6fe5ff6..08281bc 100644 --- a/scripts/rateit.ts +++ b/scripts/rateit.ts @@ -15,18 +15,6 @@ export interface RateItOptions { icon?: string; } -export class DateSetWrapper { - _item: HTMLElement; - // constructor(item: HTMLElement) { - // this._item = item; - // } - - public getItemNumber = (key: RateItItemDataTypes): number => Number(this._item.dataset[key]); - public getItemBoolean = (key: RateItItemDataTypes): boolean => Boolean(this._item.dataset[key]); - public getItemString = (key: RateItItemDataTypes): string => this._item.dataset[key]; - public setItem = (key: RateItItemDataTypes, value: any) => this._item.dataset[key] = value.toString(); -} - interface DataSetWrapper { getNumber(key: RateItItemDataTypes): number; @@ -37,7 +25,6 @@ interface DataSetWrapper { function getDataSetWrapper(item: HTMLElement): DataSetWrapper { return { - getNumber: (key: RateItItemDataTypes): number => Number(this._item.dataset[key]), getBoolean: (key: RateItItemDataTypes): boolean => Boolean(this._item.dataset[key]), getString: (key: RateItItemDataTypes): string => this._item.dataset[key], @@ -46,6 +33,7 @@ function getDataSetWrapper(item: HTMLElement): DataSetWrapper { } type RateItItemDataTypes = "init" | "value" | "min" | "max" | "step" | "backingfld" | "readonly" | "ispreset" | "resetable" | "starwidth" | "starheight" | "mode" | "icon"; + export class rateit { private index: number = 0; @@ -80,7 +68,7 @@ export class rateit { let item = items[i]; //let dataSet = new DataSetWrapper(item); let dataset: DataSetWrapper = getDataSetWrapper(item); - + item.classList.add('rateit'); let ltr = item.style.direction === 'ltr'; @@ -126,8 +114,8 @@ export class rateit { } else { dataset.set('min', (!isNaN(dataset.getNumber('min')) ? dataset.getNumber('min') : 0)); - dataset.set('max', Number(select.options[select.length - 1].value)); - dataset.set('step', Number(select.options[1].value) - Number(select.options[0].value)); + dataset.set('max', Number(select.options.item(select.length - 1).value)); + dataset.set('step', Number(select.options.item(1).value) - Number(select.options.item(0).value)); } //see if we have a option that as explicity been selected var selectedOption = select.querySelector('option[selected]'); @@ -179,9 +167,23 @@ export class rateit { } - private redraw(item: HTMLElement) { - var dataset = getDataSetWrapper(item); - + private redraw(element: HTMLElement) { + var dataset = getDataSetWrapper(element); + + var isfont = dataset.getString('mode') === 'font'; + + + //resize the height of all elements, + if (!isfont) { + Array.prototype.forEach.call(element.querySelectorAll('.rateit-selected, .rateit-hover'), function (item : HTMLElement) { + item.style.height = `${dataset.getNumber('starheight')}px`; + }); + + } + + + + } public reset(element: HTMLElement): void @@ -194,8 +196,12 @@ export class rateit { element = selectorOrElement; } + let dataset: DataSetWrapper = getDataSetWrapper(element); + + + + - } diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json index 4e25f6f..5606d7f 100644 --- a/scripts/tsconfig.json +++ b/scripts/tsconfig.json @@ -4,6 +4,7 @@ "noImplicitAny": true, "removeComments": true, "preserveConstEnums": true, + "target": "es5", "sourceMap": true }, From 54f39c108ba9db821ea63255aedd007ad8768790 Mon Sep 17 00:00:00 2001 From: Gidon Date: Wed, 19 Apr 2017 16:14:16 +0300 Subject: [PATCH 05/11] Move over more functionality from jquery.rateit.js --- gulpfile.js | 2 +- scripts/rateit.ts | 307 +++++++++++++++++++++++-- scripts/tsconfig.json => tsconfig.json | 3 +- 3 files changed, 285 insertions(+), 27 deletions(-) rename scripts/tsconfig.json => tsconfig.json (89%) diff --git a/gulpfile.js b/gulpfile.js index 146397a..d0b99a1 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -11,7 +11,7 @@ var sourcemaps = require('gulp-sourcemaps'); var typescript = require('gulp-tsc'); gulp.task('compile', function(){ gulp.src(['scripts/*.ts']) - .pipe(typescript()) + .pipe(typescript({ target: 'es5'})) .pipe(gulp.dest('scripts/')) }); diff --git a/scripts/rateit.ts b/scripts/rateit.ts index 08281bc..cf47b16 100644 --- a/scripts/rateit.ts +++ b/scripts/rateit.ts @@ -25,16 +25,23 @@ interface DataSetWrapper { function getDataSetWrapper(item: HTMLElement): DataSetWrapper { return { - getNumber: (key: RateItItemDataTypes): number => Number(this._item.dataset[key]), - getBoolean: (key: RateItItemDataTypes): boolean => Boolean(this._item.dataset[key]), - getString: (key: RateItItemDataTypes): string => this._item.dataset[key], - set: (key: RateItItemDataTypes, value: any) => this._item.dataset[key] = value.toString() + getNumber: (key: RateItItemDataTypes): number => Number(item.dataset[key]), + getBoolean: (key: RateItItemDataTypes): boolean => item.dataset[key] === 'true', + getString: (key: RateItItemDataTypes): string => item.dataset[key], + set: (key: RateItItemDataTypes, value: any) => { + if (value == null) { + delete item.dataset[key]; //might not work in safari http://stackoverflow.com/questions/9200712/how-to-remove-data-attributes-using-html5-dataset + } + else { + item.dataset[key] = value.toString(); + } + } }; } -type RateItItemDataTypes = "init" | "value" | "min" | "max" | "step" | "backingfld" | "readonly" | "ispreset" | "resetable" | "starwidth" | "starheight" | "mode" | "icon"; +type RateItItemDataTypes = "init" | "value" | "min" | "max" | "step" | "backingfld" | "readonly" | "ispreset" | "resetable" | "starwidth" | "starheight" | "mode" | "icon" | "wired"; -export class rateit { +export default class rateit { private index: number = 0; @@ -47,10 +54,13 @@ export class rateit { starwidth: 16, starheight: 16, readonly: false, - resetable: false, + resetable: true, ispreset: false }; + public get resetLabel(): string { return 'reset rating' }; + + public get ratingLabel(): string { return 'rating' }; public enable(): void public enable(selector: string): void @@ -61,9 +71,6 @@ export class rateit { let items = document.querySelectorAll(selector); - - - for (var i = 0; i < items.length; i++) { let item = items[i]; //let dataSet = new DataSetWrapper(item); @@ -71,7 +78,7 @@ export class rateit { item.classList.add('rateit'); - let ltr = item.style.direction === 'ltr'; + let ltr = window.getComputedStyle(item).direction === 'ltr'; dataset.set('mode', dataset.getNumber('mode') || options.mode) dataset.set('icon', dataset.getNumber('icon') || options.icon) @@ -140,7 +147,7 @@ export class rateit { let element = item.nodeName === 'DIV' ? 'div' : 'span'; this.index++; - var html = `<${element} id="rateit-range-${this.index}" class="rateit-range" tabindex="0" role="slider" aria-label="' + $.rateit.aria.ratingLabel + '" aria-owns="rateit-reset-${this.index}" aria-valuemin="' + itemdata('min') + '" aria-valuemax="' + itemdata('max') + '" aria-valuenow="' + itemdata('value') + '"><${element} class="rateit-empty"><${element} class="rateit-selected"><${element} class="rateit-hover">`; + var html = `<${element} id="rateit-range-${this.index}" class="rateit-range" tabindex="0" role="slider" aria-label="${this.ratingLabel}" aria-owns="rateit-reset-${this.index}" aria-valuemin="${dataset.getNumber('min')}" aria-valuemax="${dataset.getNumber('max')}" aria-valuenow="${dataset.getNumber('value')}"><${element} class="rateit-empty"><${element} class="rateit-selected"><${element} class="rateit-hover">`; item.insertAdjacentHTML('beforeend', html); if (!ltr) { @@ -158,24 +165,125 @@ export class rateit { item.classList.remove('rateit-font'); } + //setup the reset button + const resetbtn = item.querySelector('.rateit-reset'); + resetbtn.addEventListener('click', (ev) => { + + resetbtn.blur(); + const beforeResetEvent = new Event('beforereset'); + + //if the beforereset event was cancelled then do not continue resetting + if (!item.dispatchEvent(beforeResetEvent)) { + return false; + } + + this.value(item, null); + item.dispatchEvent(new Event('reset')); + + + + return false; + }, false); + + //setup other events + const range = item.querySelector('.rateit-range'); + + range.addEventListener('touchmove', rateit.touchHandler, false); + range.addEventListener('touchend', rateit.touchHandler, false); + range.addEventListener('mousemove', (e: MouseEvent) => { + var score = this.calcRawScore(item, e); + this.setHover(item, score); + }); + + //when the mouse leaves the range, we have to hide the hover stars, and show the current value. + range.addEventListener('mouseleave', (e: MouseEvent) => { + const rateitHover = ((range.querySelector('.rateit-hover'))); + rateitHover.style.display = 'none'; + rateitHover.style.width = '0'; + rateitHover.dataset['width'] = '';; + item.dispatchEvent(new CustomEvent('hover')); + item.dispatchEvent(new CustomEvent('over')); + + ((range.querySelector('.rateit-selected'))).style.display = 'block'; + }); + //when we click on the range, we have to set the value, hide the hover. + range.addEventListener('mouseup', (e: MouseEvent) => { + var score = this.calcRawScore(item, e); + var value = (score * dataset.getNumber('step')) + dataset.getNumber('min'); + this.value(item, value, true); + range.blur(); + }); + + //support key nav + range.addEventListener('keyup', (e: KeyboardEvent) => { + if (e.which == 38 || e.which == (ltr ? 39 : 37)) { + const value = Math.min(dataset.getNumber('value') + dataset.getNumber('step'), dataset.getNumber('max')); + this.value(item, value, true); + } + if (e.which == 40 || e.which == (ltr ? 37 : 39)) { + const value = Math.max(dataset.getNumber('value') - dataset.getNumber('step'), dataset.getNumber('min')); + this.value(element, value, true); + } + }); + + dataset.set('init', JSON.stringify(item.dataset));//cheap way to create a clone this.redraw(item); } + } + private setHover(element : HTMLElement, score: number): void { + const dataset = getDataSetWrapper(element); + const range = element.querySelector('.rateit-range'); + + var w = score * dataset.getNumber('starwidth') * dataset.getNumber('step'); + var h = range.querySelector('.rateit-hover'); + if (h.dataset['width'] != w.toString()) { + ((range.querySelector('.rateit-selected'))).style.display = 'none'; + h.style.width = `${w}px`; + h.style.display = 'block'; + h.dataset['width'] = w.toString(); + const value = [(score * dataset.getNumber('step')) + dataset.getNumber('min')]; + var ev = new CustomEvent('hover', { + detail: { + value: (score * dataset.getNumber('step')) + dataset.getNumber('min') + } + }) + element.dispatchEvent(new CustomEvent('hover', { detail: { value: value } })); + element.dispatchEvent(new CustomEvent('over', { detail: { value: value } })); + } + } - + private calcRawScore(el: HTMLElement, ev: UIEvent): number { + const pageX: number = (event.type.substring(0, 5) === 'touch') + ? (ev).changedTouches[0].pageX + : (ev).pageX; + const dataset = getDataSetWrapper(el); + const range = el.querySelector('.rateit-range'); + const rangeComputedStyle = window.getComputedStyle(range); + + const offsetLeft = range.getBoundingClientRect().left + document.body.scrollLeft; + const rangeWidth = parseInt(rangeComputedStyle.width); + let offsetx = pageX - offsetLeft; + if (rangeComputedStyle.direction === 'rtl') { offsetx = parseInt(rangeComputedStyle.width) - offsetx }; + if (offsetx > rangeWidth) { offsetx = rangeWidth; } + if (offsetx < 0) { offsetx = 0; } + + return Math.ceil(offsetx / dataset.getNumber('starwidth') * (1 / dataset.getNumber('step'))); } private redraw(element: HTMLElement) { - var dataset = getDataSetWrapper(element); - - var isfont = dataset.getString('mode') === 'font'; + const dataset = getDataSetWrapper(element); + const isfont = dataset.getString('mode') === 'font'; + const range = element.querySelector('.rateit-range'); + let rangeComputedStyle = window.getComputedStyle(range); + const ltr = rangeComputedStyle.direction === 'ltr'; //resize the height of all elements, if (!isfont) { - Array.prototype.forEach.call(element.querySelectorAll('.rateit-selected, .rateit-hover'), function (item : HTMLElement) { + Array.prototype.forEach.call(element.querySelectorAll('.rateit-selected, .rateit-hover'), function (item: HTMLElement) { item.style.height = `${dataset.getNumber('starheight')}px`; }); @@ -183,29 +291,178 @@ export class rateit { + if (isfont) { + //fill the ranges with the icons + const icon = dataset.getString('icon'); + const stars = dataset.getNumber('max') - dataset.getNumber('min'); + + let txt = ''; + for (let i = 0; i < stars; i++) { + txt += icon; + } + + Array.prototype.forEach.call(range.querySelectorAll('> *'), function (item: HTMLElement) { + item.innerText = txt; + }); + + dataset.set('starwidth', parseInt(rangeComputedStyle.width) / (dataset.getNumber('max') - dataset.getNumber('min'))); + + } + else { + //set the range element to fit all the stars. + range.style.width = `${dataset.getNumber('starwidth') * (dataset.getNumber('max') - dataset.getNumber('min'))}px`; + range.style.height = `${dataset.getNumber('starheight')}px`; + } + + + //add/remove the preset class + const presetclass = `rateit-preset${(ltr) ? '' : '-rtl'}`; + if (dataset.getBoolean('ispreset')) { + element.querySelector('.rateit-selected').classList.add(presetclass); + } + else { + element.querySelector('.rateit-selected').classList.remove(presetclass); + } + + //set the value if we have it. + if (dataset.getNumber('value') != null) { + var score = (dataset.getNumber('value') - dataset.getNumber('min')) * dataset.getNumber('starwidth'); + ((element.querySelector('.rateit-selected'))).style.width = `${score}px`; + } + + + + + + + //sets the hover element based on the score. + + + const resetbtn = element.querySelector('.rateit-reset'); + if (!dataset.getBoolean('readonly')) { + //if we are not read only, add all the events + + //if we have a reset button, set the event handler. + if (!dataset.getBoolean('resetable')) { + resetbtn.style.display = 'none'; + } + + if (dataset.getBoolean('resetable')) { + resetbtn.style.display = 'block'; + } + } + else { + resetbtn.style.display = 'none'; + } + + range.setAttribute('aria-readonly', dataset.getString('readonly')); } + public value(element: HTMLElement): Number + public value(selector: HTMLElement): Number + public value(element: HTMLElement, value: number): boolean + public value(selector: string, value: number): boolean + public value(element: HTMLElement, value: number, raiseEvents: boolean): boolean + public value(selector: string, value: number, raiseEvents: boolean): boolean + public value(selectorOrElement: any, value?: number, raiseEvents?: boolean): any { + const element: HTMLElement = rateit.getElementFromSelectorOrElement(selectorOrElement); + const dataset: DataSetWrapper = getDataSetWrapper(element); + + if (typeof (value) === 'undefined') + return dataset.getNumber('value'); + + const range = element.querySelector('.rateit-range'); + const rangeComputedStyle = window.getComputedStyle(range); + const presetclass = `rateit-preset${(rangeComputedStyle.direction === 'ltr') ? '' : '-rtl'}`; + + if (raiseEvents === true) { + const event = new CustomEvent('beforerated', { detail: { value: value } }); // has to be CustomEvent + + if (!element.dispatchEvent(event)) { + return false; + } + } + + dataset.set('value', value); + + if (dataset.getString('backingfld') != null) { + // If backing field is a select box with valuesrc option set to "index", update its selectedIndex property; otherwise, update its value. + const backingFld = document.querySelector(dataset.getString('backingfld')); + + if (backingFld.nodeName == 'SELECT' && backingFld.getAttribute('data-rateit-valuesrc') === 'index') { + (backingFld).selectedIndex = value; + } + else { + (backingFld).value = value.toString(); + + } + + if (raiseEvents === true) { + const evt = new Event('change', { bubbles: true, cancelable: false }); + backingFld.dispatchEvent(evt); + } + + } + if (dataset.getBoolean('ispreset')) { //if it was a preset value, unset that. + ((range.querySelector('.rateit-selected'))).classList.remove(presetclass); + dataset.set('ispreset', false); + } + + + ((range.querySelector('.rateit-hover'))).style.display = 'none'; + const rateItSelected = ((range.querySelector('.rateit-selected'))); + rateItSelected.style.width = `${value * dataset.getNumber('starwidth') - (dataset.getNumber('min') * dataset.getNumber('starwidth'))}px`; + rateItSelected.style.display = 'block'; + + if (raiseEvents === true) { + element.dispatchEvent(new CustomEvent('hover')); + element.dispatchEvent(new CustomEvent('over')); + element.dispatchEvent(new CustomEvent('rated', { detail: { value: value } })); + } + + return true; + } + + public reset(element: HTMLElement): void public reset(selector: string): void public reset(selectorOrElement: any): void { - let element: HTMLElement; + const element: HTMLElement = rateit.getElementFromSelectorOrElement(selectorOrElement); + const dataset: DataSetWrapper = getDataSetWrapper(element); + + } + + private static getElementFromSelectorOrElement(selectorOrElement: any): HTMLElement { if (typeof (selectorOrElement) === 'string') { - element = document.querySelector(selectorOrElement); + return document.querySelector(selectorOrElement); } else { - element = selectorOrElement; + return selectorOrElement; } + } - let dataset: DataSetWrapper = getDataSetWrapper(element); - - + //touch converter http://ross.posterous.com/2008/08/19/iphone-touch-events-in-javascript/ + private static touchHandler(event: TouchEvent): void { + var touches = event.changedTouches, + first = touches[0], + type = ""; + switch (event.type) { + case "touchmove": type = "mousemove"; break; + case "touchend": type = "mouseup"; break; + default: return; + } + var simulatedEvent = document.createEvent("MouseEvent"); + simulatedEvent.initMouseEvent(type, true, true, window, 1, + first.screenX, first.screenY, + first.clientX, first.clientY, false, + false, false, false, 0/*left*/, null); + first.target.dispatchEvent(simulatedEvent); + event.preventDefault(); } - - } diff --git a/scripts/tsconfig.json b/tsconfig.json similarity index 89% rename from scripts/tsconfig.json rename to tsconfig.json index 5606d7f..da04162 100644 --- a/scripts/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "module": "commonjs", + "noImplicitAny": true, "removeComments": true, "preserveConstEnums": true, @@ -10,6 +11,6 @@ }, "compileOnSave": true, "files": [ - "rateit.ts" + "./scripts/rateit.ts" ] } \ No newline at end of file From b5cb5abf8297bbd751101d70c5b0835b8bd6fee5 Mon Sep 17 00:00:00 2001 From: Gidon Date: Wed, 19 Apr 2017 16:26:35 +0300 Subject: [PATCH 06/11] Fix illegal query selector --- scripts/rateit.ts | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/scripts/rateit.ts b/scripts/rateit.ts index cf47b16..642de05 100644 --- a/scripts/rateit.ts +++ b/scripts/rateit.ts @@ -24,16 +24,19 @@ interface DataSetWrapper { } function getDataSetWrapper(item: HTMLElement): DataSetWrapper { + const createKey = function (key: string) { + return 'rateit' + key.charAt(0).toUpperCase() + key.substr(1); + }; return { - getNumber: (key: RateItItemDataTypes): number => Number(item.dataset[key]), - getBoolean: (key: RateItItemDataTypes): boolean => item.dataset[key] === 'true', - getString: (key: RateItItemDataTypes): string => item.dataset[key], + getNumber: (key: RateItItemDataTypes): number => Number(item.dataset[createKey(key)]), + getBoolean: (key: RateItItemDataTypes): boolean => item.dataset[createKey(key)] === 'true', + getString: (key: RateItItemDataTypes): string => item.dataset[createKey(key)], set: (key: RateItItemDataTypes, value: any) => { if (value == null) { - delete item.dataset[key]; //might not work in safari http://stackoverflow.com/questions/9200712/how-to-remove-data-attributes-using-html5-dataset + delete item.dataset[createKey(key)]; //might not work in safari http://stackoverflow.com/questions/9200712/how-to-remove-data-attributes-using-html5-dataset } else { - item.dataset[key] = value.toString(); + item.dataset[createKey(key)] = value.toString(); } } }; @@ -80,8 +83,8 @@ export default class rateit { let ltr = window.getComputedStyle(item).direction === 'ltr'; - dataset.set('mode', dataset.getNumber('mode') || options.mode) - dataset.set('icon', dataset.getNumber('icon') || options.icon) + dataset.set('mode', dataset.getString('mode') || options.mode) + dataset.set('icon', dataset.getString('icon') || options.icon) dataset.set('min', isNaN(dataset.getNumber('min')) ? options.min : dataset.getNumber('min')) dataset.set('max', isNaN(dataset.getNumber('max')) ? options.max : dataset.getNumber('max')) dataset.set('step', item.dataset['step'] || options.step) @@ -286,11 +289,8 @@ export default class rateit { Array.prototype.forEach.call(element.querySelectorAll('.rateit-selected, .rateit-hover'), function (item: HTMLElement) { item.style.height = `${dataset.getNumber('starheight')}px`; }); - } - - if (isfont) { //fill the ranges with the icons const icon = dataset.getString('icon'); @@ -301,7 +301,7 @@ export default class rateit { txt += icon; } - Array.prototype.forEach.call(range.querySelectorAll('> *'), function (item: HTMLElement) { + Array.prototype.forEach.call(range.children, function (item: HTMLElement) { item.innerText = txt; }); @@ -431,6 +431,7 @@ export default class rateit { const element: HTMLElement = rateit.getElementFromSelectorOrElement(selectorOrElement); const dataset: DataSetWrapper = getDataSetWrapper(element); + } private static getElementFromSelectorOrElement(selectorOrElement: any): HTMLElement { From 780ec50b98d676996194b5e42f6d847976cc55e1 Mon Sep 17 00:00:00 2001 From: Gidon Date: Wed, 19 Apr 2017 17:52:29 +0300 Subject: [PATCH 07/11] add options method --- scripts/rateit.ts | 217 +++++++++++++++++++++++++++------------------- 1 file changed, 127 insertions(+), 90 deletions(-) diff --git a/scripts/rateit.ts b/scripts/rateit.ts index 642de05..ef6a6de 100644 --- a/scripts/rateit.ts +++ b/scripts/rateit.ts @@ -13,6 +13,7 @@ export interface RateItOptions { starheight?: number; mode?: RateItMode; icon?: string; + [key: string]: any; } interface DataSetWrapper { @@ -24,9 +25,9 @@ interface DataSetWrapper { } function getDataSetWrapper(item: HTMLElement): DataSetWrapper { - const createKey = function (key: string) { - return 'rateit' + key.charAt(0).toUpperCase() + key.substr(1); - }; + const createKey = function (key: string) { + return 'rateit' + key.charAt(0).toUpperCase() + key.substr(1); + }; return { getNumber: (key: RateItItemDataTypes): number => Number(item.dataset[createKey(key)]), getBoolean: (key: RateItItemDataTypes): boolean => item.dataset[createKey(key)] === 'true', @@ -42,7 +43,7 @@ function getDataSetWrapper(item: HTMLElement): DataSetWrapper { }; } -type RateItItemDataTypes = "init" | "value" | "min" | "max" | "step" | "backingfld" | "readonly" | "ispreset" | "resetable" | "starwidth" | "starheight" | "mode" | "icon" | "wired"; +type RateItItemDataTypes = "init" | "value" | "min" | "max" | "step" | "backingfld" | "readonly" | "ispreset" | "resetable" | "starwidth" | "starheight" | "mode" | "icon"; export default class rateit { @@ -59,6 +60,7 @@ export default class rateit { readonly: false, resetable: true, ispreset: false + }; public get resetLabel(): string { return 'reset rating' }; @@ -159,15 +161,6 @@ export default class rateit { (item.querySelector('.rateit-hover')).classList.add('rateit-hover-rtl'); } - if (dataset.getString('mode') == 'font') { - item.classList.add('rateit-font'); - item.classList.remove('rateit-bg'); - } - else { - item.classList.add('rateit-bg'); - item.classList.remove('rateit-font'); - } - //setup the reset button const resetbtn = item.querySelector('.rateit-reset'); resetbtn.addEventListener('click', (ev) => { @@ -236,10 +229,114 @@ export default class rateit { } } - private setHover(element : HTMLElement, score: number): void { + public options(selector: string, options: RateItOptions): void + public options(element: HTMLElement, options: RateItOptions): void + public options(selector: string): RateItOptions + public options(element: HTMLElement): RateItOptions + public options(selectorOrElement: any, options?: RateItOptions): any { + const element: HTMLElement = rateit.getElementFromSelectorOrElement(selectorOrElement); const dataset = getDataSetWrapper(element); + if (typeof (options) === 'undefined') { + let opts: any = {}; + for (var property in element.dataset) { + if (element.dataset.hasOwnProperty(property) && property !== 'rateitInit') { + opts[property.substring('rateit'.length).toLowerCase()] = element.dataset[property]; + } + } + return opts; + } + + for (var property in options) { + if (options.hasOwnProperty(property) && property !== 'init') { + dataset.set(property, options[property]); + } + } + + this.redraw(element); + + } + + + public value(element: HTMLElement): Number + public value(selector: HTMLElement): Number + public value(element: HTMLElement, value: number): boolean + public value(selector: string, value: number): boolean + public value(element: HTMLElement, value: number, raiseEvents: boolean): boolean + public value(selector: string, value: number, raiseEvents: boolean): boolean + public value(selectorOrElement: any, value?: number, raiseEvents?: boolean): any { + const element: HTMLElement = rateit.getElementFromSelectorOrElement(selectorOrElement); + const dataset: DataSetWrapper = getDataSetWrapper(element); + + if (typeof (value) === 'undefined') + return dataset.getNumber('value'); + const range = element.querySelector('.rateit-range'); - + const rangeComputedStyle = window.getComputedStyle(range); + const presetclass = `rateit-preset${(rangeComputedStyle.direction === 'ltr') ? '' : '-rtl'}`; + + if (raiseEvents === true) { + const event = new CustomEvent('beforerated', { detail: { value: value } }); // has to be CustomEvent + + if (!element.dispatchEvent(event)) { + return false; + } + } + + dataset.set('value', value); + + if (dataset.getString('backingfld') != null) { + // If backing field is a select box with valuesrc option set to "index", update its selectedIndex property; otherwise, update its value. + const backingFld = document.querySelector(dataset.getString('backingfld')); + + if (backingFld.nodeName == 'SELECT' && backingFld.getAttribute('data-rateit-valuesrc') === 'index') { + (backingFld).selectedIndex = value; + } + else { + (backingFld).value = value.toString(); + + } + + if (raiseEvents === true) { + const evt = new Event('change', { bubbles: true, cancelable: false }); + backingFld.dispatchEvent(evt); + } + + } + if (dataset.getBoolean('ispreset')) { //if it was a preset value, unset that. + ((range.querySelector('.rateit-selected'))).classList.remove(presetclass); + dataset.set('ispreset', false); + } + + + ((range.querySelector('.rateit-hover'))).style.display = 'none'; + const rateItSelected = ((range.querySelector('.rateit-selected'))); + rateItSelected.style.width = `${value * dataset.getNumber('starwidth') - (dataset.getNumber('min') * dataset.getNumber('starwidth'))}px`; + rateItSelected.style.display = 'block'; + + if (raiseEvents === true) { + element.dispatchEvent(new CustomEvent('hover')); + element.dispatchEvent(new CustomEvent('over')); + element.dispatchEvent(new CustomEvent('rated', { detail: { value: value } })); + } + + return true; + } + + + public reset(element: HTMLElement): void + public reset(selector: string): void + public reset(selectorOrElement: any): void { + const element: HTMLElement = rateit.getElementFromSelectorOrElement(selectorOrElement); + const dataset: DataSetWrapper = getDataSetWrapper(element); + + + } + + + private setHover(element: HTMLElement, score: number): void { + const dataset = getDataSetWrapper(element); + const range = element.querySelector('.rateit-range'); + var w = score * dataset.getNumber('starwidth') * dataset.getNumber('step'); var h = range.querySelector('.rateit-hover'); if (h.dataset['width'] != w.toString()) { @@ -276,6 +373,7 @@ export default class rateit { return Math.ceil(offsetx / dataset.getNumber('starwidth') * (1 / dataset.getNumber('step'))); } + private redraw(element: HTMLElement) { const dataset = getDataSetWrapper(element); @@ -292,6 +390,10 @@ export default class rateit { } if (isfont) { + + element.classList.add('rateit-font'); + element.classList.remove('rateit-bg'); + //fill the ranges with the icons const icon = dataset.getString('icon'); const stars = dataset.getNumber('max') - dataset.getNumber('min'); @@ -309,12 +411,22 @@ export default class rateit { } else { + + element.classList.add('rateit-bg'); + element.classList.remove('rateit-font'); + + Array.prototype.forEach.call(range.children, function (item: HTMLElement) { + item.innerText = ''; + }); + //set the range element to fit all the stars. range.style.width = `${dataset.getNumber('starwidth') * (dataset.getNumber('max') - dataset.getNumber('min'))}px`; range.style.height = `${dataset.getNumber('starheight')}px`; } + + //add/remove the preset class const presetclass = `rateit-preset${(ltr) ? '' : '-rtl'}`; if (dataset.getBoolean('ispreset')) { @@ -359,81 +471,6 @@ export default class rateit { } - public value(element: HTMLElement): Number - public value(selector: HTMLElement): Number - public value(element: HTMLElement, value: number): boolean - public value(selector: string, value: number): boolean - public value(element: HTMLElement, value: number, raiseEvents: boolean): boolean - public value(selector: string, value: number, raiseEvents: boolean): boolean - public value(selectorOrElement: any, value?: number, raiseEvents?: boolean): any { - const element: HTMLElement = rateit.getElementFromSelectorOrElement(selectorOrElement); - const dataset: DataSetWrapper = getDataSetWrapper(element); - - if (typeof (value) === 'undefined') - return dataset.getNumber('value'); - - const range = element.querySelector('.rateit-range'); - const rangeComputedStyle = window.getComputedStyle(range); - const presetclass = `rateit-preset${(rangeComputedStyle.direction === 'ltr') ? '' : '-rtl'}`; - - if (raiseEvents === true) { - const event = new CustomEvent('beforerated', { detail: { value: value } }); // has to be CustomEvent - - if (!element.dispatchEvent(event)) { - return false; - } - } - - dataset.set('value', value); - - if (dataset.getString('backingfld') != null) { - // If backing field is a select box with valuesrc option set to "index", update its selectedIndex property; otherwise, update its value. - const backingFld = document.querySelector(dataset.getString('backingfld')); - - if (backingFld.nodeName == 'SELECT' && backingFld.getAttribute('data-rateit-valuesrc') === 'index') { - (backingFld).selectedIndex = value; - } - else { - (backingFld).value = value.toString(); - - } - - if (raiseEvents === true) { - const evt = new Event('change', { bubbles: true, cancelable: false }); - backingFld.dispatchEvent(evt); - } - - } - if (dataset.getBoolean('ispreset')) { //if it was a preset value, unset that. - ((range.querySelector('.rateit-selected'))).classList.remove(presetclass); - dataset.set('ispreset', false); - } - - - ((range.querySelector('.rateit-hover'))).style.display = 'none'; - const rateItSelected = ((range.querySelector('.rateit-selected'))); - rateItSelected.style.width = `${value * dataset.getNumber('starwidth') - (dataset.getNumber('min') * dataset.getNumber('starwidth'))}px`; - rateItSelected.style.display = 'block'; - - if (raiseEvents === true) { - element.dispatchEvent(new CustomEvent('hover')); - element.dispatchEvent(new CustomEvent('over')); - element.dispatchEvent(new CustomEvent('rated', { detail: { value: value } })); - } - - return true; - } - - - public reset(element: HTMLElement): void - public reset(selector: string): void - public reset(selectorOrElement: any): void { - const element: HTMLElement = rateit.getElementFromSelectorOrElement(selectorOrElement); - const dataset: DataSetWrapper = getDataSetWrapper(element); - - - } - private static getElementFromSelectorOrElement(selectorOrElement: any): HTMLElement { if (typeof (selectorOrElement) === 'string') { return document.querySelector(selectorOrElement); From 00f9da394b127e2902bd36b841fd19feadf33ae9 Mon Sep 17 00:00:00 2001 From: Gidon Date: Thu, 20 Apr 2017 16:21:29 +0300 Subject: [PATCH 08/11] Move .ts to src folder Update gulpfile.js to build from src folder into scripts Fix gulpfile.js to minify both rateit.js and jquery.rateit.js --- .gitignore | 5 ++++- gulpfile.js | 4 ++-- {scripts => src}/rateit.ts | 0 3 files changed, 6 insertions(+), 3 deletions(-) rename {scripts => src}/rateit.ts (100%) diff --git a/.gitignore b/.gitignore index 61b81d1..620c5d0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,7 @@ node_modules bower_components test.htm -.vs \ No newline at end of file + +# Editors +.vs +.vscode \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index d0b99a1..c9e9bf7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -10,13 +10,13 @@ var rename = require('gulp-rename'); var sourcemaps = require('gulp-sourcemaps'); var typescript = require('gulp-tsc'); gulp.task('compile', function(){ - gulp.src(['scripts/*.ts']) + gulp.src(['src/*.ts']) .pipe(typescript({ target: 'es5'})) .pipe(gulp.dest('scripts/')) }); gulp.task('minify', function () { - return gulp.src('scripts/*rateit.js') + return gulp.src(['scripts/rateit.js','scripts/jquery.rateit.js']) .pipe(sourcemaps.init()) .pipe(plumber()) .pipe(uglify({ preserveComments: 'license' })) diff --git a/scripts/rateit.ts b/src/rateit.ts similarity index 100% rename from scripts/rateit.ts rename to src/rateit.ts From 4b3fe3e8d8c62532a5b6f211104e4aa75f66e773 Mon Sep 17 00:00:00 2001 From: Gidon Date: Thu, 20 Apr 2017 17:15:30 +0300 Subject: [PATCH 09/11] rateit.ts * fix ispreset * call value() when value is passed in options() * reset() => revert() + implementation * code cleaning --- src/rateit.ts | 144 ++++++++++++++++++++++++++------------------------ 1 file changed, 75 insertions(+), 69 deletions(-) diff --git a/src/rateit.ts b/src/rateit.ts index ef6a6de..0799ed9 100644 --- a/src/rateit.ts +++ b/src/rateit.ts @@ -1,4 +1,5 @@ export type RateItMode = "bg" | "font"; +type RateItItemDataTypes = "init" | "value" | "min" | "max" | "step" | "backingfld" | "readonly" | "ispreset" | "resetable" | "starwidth" | "starheight" | "mode" | "icon"; export interface RateItOptions { value?: number; @@ -16,35 +17,6 @@ export interface RateItOptions { [key: string]: any; } -interface DataSetWrapper { - - getNumber(key: RateItItemDataTypes): number; - getBoolean(key: RateItItemDataTypes): boolean; - getString(key: RateItItemDataTypes): string; - set(key: RateItItemDataTypes, value: any): void; -} - -function getDataSetWrapper(item: HTMLElement): DataSetWrapper { - const createKey = function (key: string) { - return 'rateit' + key.charAt(0).toUpperCase() + key.substr(1); - }; - return { - getNumber: (key: RateItItemDataTypes): number => Number(item.dataset[createKey(key)]), - getBoolean: (key: RateItItemDataTypes): boolean => item.dataset[createKey(key)] === 'true', - getString: (key: RateItItemDataTypes): string => item.dataset[createKey(key)], - set: (key: RateItItemDataTypes, value: any) => { - if (value == null) { - delete item.dataset[createKey(key)]; //might not work in safari http://stackoverflow.com/questions/9200712/how-to-remove-data-attributes-using-html5-dataset - } - else { - item.dataset[createKey(key)] = value.toString(); - } - } - }; -} - -type RateItItemDataTypes = "init" | "value" | "min" | "max" | "step" | "backingfld" | "readonly" | "ispreset" | "resetable" | "starwidth" | "starheight" | "mode" | "icon"; - export default class rateit { private index: number = 0; @@ -79,24 +51,24 @@ export default class rateit { for (var i = 0; i < items.length; i++) { let item = items[i]; //let dataSet = new DataSetWrapper(item); - let dataset: DataSetWrapper = getDataSetWrapper(item); + let dataset: DataSetWrapper = rateit.getDataSetWrapper(item); item.classList.add('rateit'); let ltr = window.getComputedStyle(item).direction === 'ltr'; - dataset.set('mode', dataset.getString('mode') || options.mode) - dataset.set('icon', dataset.getString('icon') || options.icon) - dataset.set('min', isNaN(dataset.getNumber('min')) ? options.min : dataset.getNumber('min')) - dataset.set('max', isNaN(dataset.getNumber('max')) ? options.max : dataset.getNumber('max')) - dataset.set('step', item.dataset['step'] || options.step) - dataset.set('resetable', item.dataset['resetable'] !== undefined ? item.dataset['resetable'] : options.resetable) - dataset.set('readonly', item.dataset['readonly'] !== undefined ? item.dataset['readonly'] : options.readonly) - dataset.set('ispreset', item.dataset['ispreset'] !== undefined ? item.dataset['ispreset'] : options.ispreset) - dataset.set('backingfld', item.dataset['backingfld'] || options.backingfld) - dataset.set('starwidth', dataset.getNumber('starwidth') || options.starwidth) - dataset.set('starheight', dataset.getNumber('starheight') || options.starheight) - dataset.set('value', Math.max(dataset.getNumber('min'), Math.min(dataset.getNumber('max'), (!isNaN(dataset.getNumber('value')) ? dataset.getNumber('value') : options.value == null ? options.min : options.value)))) + dataset.set('mode', dataset.getString('mode') || options.mode); + dataset.set('icon', dataset.getString('icon') || options.icon); + dataset.set('min', isNaN(dataset.getNumber('min')) ? options.min : dataset.getNumber('min')); //since 0 is a valid entry, we check for isNaN + dataset.set('max', isNaN(dataset.getNumber('max')) ? options.max : dataset.getNumber('max'));//since 0 is a valid entry, we check for isNaN + dataset.set('step', dataset.getNumber('step') || options.step); + dataset.set('resetable', dataset.getString('resetable') !== undefined ? dataset.getBoolean('resetable') : options.resetable); + dataset.set('readonly', dataset.getString('readonly') !== undefined ? dataset.getBoolean('readonly') : options.readonly); + dataset.set('ispreset', dataset.getString('ispreset') !== undefined ? dataset.getBoolean('ispreset') : options.ispreset); + dataset.set('backingfld', dataset.getString('backingfld') || options.backingfld); + dataset.set('starwidth', dataset.getNumber('starwidth') || options.starwidth); + dataset.set('starheight', dataset.getNumber('starheight') || options.starheight); + dataset.set('value', Math.max(dataset.getNumber('min'), Math.min(dataset.getNumber('max'), (!isNaN(dataset.getNumber('value')) ? dataset.getNumber('value') : options.value == null ? options.min : options.value)))); if (dataset.getString('backingfld')) { //if we have a backing field, hide it, override defaults if range or select. @@ -173,7 +145,14 @@ export default class rateit { return false; } - this.value(item, null); + const initialOptions = JSON.parse(dataset.getString('init')); + if (initialOptions.ispreset) { + this.options(item, { ispreset: true, value: initialOptions.value }); + } + else { + this.value(item, null); + } + item.dispatchEvent(new Event('reset')); @@ -223,7 +202,7 @@ export default class rateit { }); - dataset.set('init', JSON.stringify(item.dataset));//cheap way to create a clone + dataset.set('init', JSON.stringify(this.options(item)));//cheap way to create a clone this.redraw(item); } @@ -235,7 +214,7 @@ export default class rateit { public options(element: HTMLElement): RateItOptions public options(selectorOrElement: any, options?: RateItOptions): any { const element: HTMLElement = rateit.getElementFromSelectorOrElement(selectorOrElement); - const dataset = getDataSetWrapper(element); + const dataset = rateit.getDataSetWrapper(element); if (typeof (options) === 'undefined') { let opts: any = {}; for (var property in element.dataset) { @@ -251,7 +230,11 @@ export default class rateit { dataset.set(property, options[property]); } } - + if (options.hasOwnProperty('value')) + { + //if we also got the value passed in, call the value method, since that takes also care of setting the correct value in the backingfield. + this.value(element, options.value, false); + } this.redraw(element); } @@ -265,7 +248,7 @@ export default class rateit { public value(selector: string, value: number, raiseEvents: boolean): boolean public value(selectorOrElement: any, value?: number, raiseEvents?: boolean): any { const element: HTMLElement = rateit.getElementFromSelectorOrElement(selectorOrElement); - const dataset: DataSetWrapper = getDataSetWrapper(element); + const dataset: DataSetWrapper = rateit.getDataSetWrapper(element); if (typeof (value) === 'undefined') return dataset.getNumber('value'); @@ -323,18 +306,18 @@ export default class rateit { } - public reset(element: HTMLElement): void - public reset(selector: string): void - public reset(selectorOrElement: any): void { + public revert(element: HTMLElement): void + public revert(selector: string): void + public revert(selectorOrElement: any): void { const element: HTMLElement = rateit.getElementFromSelectorOrElement(selectorOrElement); - const dataset: DataSetWrapper = getDataSetWrapper(element); - + const dataset: DataSetWrapper = rateit.getDataSetWrapper(element); + this.options(selectorOrElement, JSON.parse(dataset.getString('init'))); } private setHover(element: HTMLElement, score: number): void { - const dataset = getDataSetWrapper(element); + const dataset = rateit.getDataSetWrapper(element); const range = element.querySelector('.rateit-range'); var w = score * dataset.getNumber('starwidth') * dataset.getNumber('step'); @@ -359,7 +342,7 @@ export default class rateit { const pageX: number = (event.type.substring(0, 5) === 'touch') ? (ev).changedTouches[0].pageX : (ev).pageX; - const dataset = getDataSetWrapper(el); + const dataset = rateit.getDataSetWrapper(el); const range = el.querySelector('.rateit-range'); const rangeComputedStyle = window.getComputedStyle(range); @@ -375,19 +358,13 @@ export default class rateit { private redraw(element: HTMLElement) { - const dataset = getDataSetWrapper(element); + const dataset = rateit.getDataSetWrapper(element); const isfont = dataset.getString('mode') === 'font'; const range = element.querySelector('.rateit-range'); let rangeComputedStyle = window.getComputedStyle(range); const ltr = rangeComputedStyle.direction === 'ltr'; - //resize the height of all elements, - if (!isfont) { - Array.prototype.forEach.call(element.querySelectorAll('.rateit-selected, .rateit-hover'), function (item: HTMLElement) { - item.style.height = `${dataset.getNumber('starheight')}px`; - }); - } if (isfont) { @@ -409,9 +386,16 @@ export default class rateit { dataset.set('starwidth', parseInt(rangeComputedStyle.width) / (dataset.getNumber('max') - dataset.getNumber('min'))); + range.style.width = ''; + range.style.height = ''; + + Array.prototype.forEach.call(element.querySelectorAll('.rateit-selected, .rateit-hover'), function (item: HTMLElement) { + item.style.height = ''; + }); } else { + element.classList.add('rateit-bg'); element.classList.remove('rateit-font'); @@ -422,10 +406,12 @@ export default class rateit { //set the range element to fit all the stars. range.style.width = `${dataset.getNumber('starwidth') * (dataset.getNumber('max') - dataset.getNumber('min'))}px`; range.style.height = `${dataset.getNumber('starheight')}px`; - } - + Array.prototype.forEach.call(element.querySelectorAll('.rateit-selected, .rateit-hover'), function (item: HTMLElement) { + item.style.height = `${dataset.getNumber('starheight')}px`; + }); + } //add/remove the preset class const presetclass = `rateit-preset${(ltr) ? '' : '-rtl'}`; @@ -443,13 +429,7 @@ export default class rateit { } - - - - //sets the hover element based on the score. - - const resetbtn = element.querySelector('.rateit-reset'); if (!dataset.getBoolean('readonly')) { //if we are not read only, add all the events @@ -501,6 +481,32 @@ export default class rateit { event.preventDefault(); } +private static getDataSetWrapper(item: HTMLElement): DataSetWrapper { + const createKey = function (key: string) { + return 'rateit' + key.charAt(0).toUpperCase() + key.substr(1); + }; + return { + getNumber: (key: RateItItemDataTypes): number => Number(item.dataset[createKey(key)]), + getBoolean: (key: RateItItemDataTypes): boolean => item.dataset[createKey(key)] === 'true', + getString: (key: RateItItemDataTypes): string => item.dataset[createKey(key)], + set: (key: RateItItemDataTypes, value: any) => { + if (value == null) { + delete item.dataset[createKey(key)]; //might not work in safari http://stackoverflow.com/questions/9200712/how-to-remove-data-attributes-using-html5-dataset + } + else { + item.dataset[createKey(key)] = value.toString(); + } + } + }; +} +} + +interface DataSetWrapper { + + getNumber(key: RateItItemDataTypes): number; + getBoolean(key: RateItItemDataTypes): boolean; + getString(key: RateItItemDataTypes): string; + set(key: RateItItemDataTypes, value: any): void; } From f3eeb3120c7b9a3e5f53b19ad4844772d05fcd65 Mon Sep 17 00:00:00 2001 From: Gidon Date: Thu, 20 Apr 2017 17:23:53 +0300 Subject: [PATCH 10/11] rateit.ts * Fix bug where preset is not reverted on clicking reset button --- src/rateit.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/rateit.ts b/src/rateit.ts index 0799ed9..28de923 100644 --- a/src/rateit.ts +++ b/src/rateit.ts @@ -144,7 +144,6 @@ export default class rateit { if (!item.dispatchEvent(beforeResetEvent)) { return false; } - const initialOptions = JSON.parse(dataset.getString('init')); if (initialOptions.ispreset) { this.options(item, { ispreset: true, value: initialOptions.value }); @@ -225,16 +224,18 @@ export default class rateit { return opts; } - for (var property in options) { - if (options.hasOwnProperty(property) && property !== 'init') { - dataset.set(property, options[property]); - } - } if (options.hasOwnProperty('value')) { //if we also got the value passed in, call the value method, since that takes also care of setting the correct value in the backingfield. this.value(element, options.value, false); } + + for (var property in options) { + if (options.hasOwnProperty(property) && property !== 'init') { + dataset.set(property, options[property]); + } + } + this.redraw(element); } From d236c91d91892c194dc4db5f8514ec50d8d9f4bf Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 21 Feb 2026 09:08:47 +0000 Subject: [PATCH 11/11] rateit.ts * Fix multiple bugs and complete vanilla JS feature parity Bug fixes: - Fix keyboard down-arrow using 'element' (string) instead of 'item' (HTMLElement) - broken since initial implementation - Fix calcRawScore using global 'event' object instead of 'ev' parameter for touch/mouse detection - Fix options() getter returning all values as strings, causing ispreset string "false" to be truthy and breaking reset button logic - Fix value(el, null) not resetting to min (now mirrors jQuery version behaviour) - Fix event listeners being bound unconditionally on readonly widgets (mousemove/mouseup/keyup now only bound when not readonly) - Fix no guard against re-initialising already-initialised elements (enable() now calls redraw() and skips if init data present) New features: - Add DOMContentLoaded auto-initialisation for all .rateit elements (mirrors jQuery plugin behaviour) Build: - Compile src/rateit.ts to scripts/rateit.js (UMD module format, ES5 target) - Update tsconfig.json: fix file reference from ./scripts/rateit.ts to ./src/rateit.ts, change module to umd - Update gulpfile.js: pass module: 'umd' to typescript compiler https://claude.ai/code/session_01HKMsCrHeceW42kMUtcJTCv --- gulpfile.js | 2 +- scripts/rateit.js | 438 ++++++++++++++++++++++++++++++++++++++++++++++ src/rateit.ts | 115 +++++++----- tsconfig.json | 4 +- 4 files changed, 511 insertions(+), 48 deletions(-) create mode 100644 scripts/rateit.js diff --git a/gulpfile.js b/gulpfile.js index c9e9bf7..6d5a8ea 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -11,7 +11,7 @@ var sourcemaps = require('gulp-sourcemaps'); var typescript = require('gulp-tsc'); gulp.task('compile', function(){ gulp.src(['src/*.ts']) - .pipe(typescript({ target: 'es5'})) + .pipe(typescript({ target: 'es5', module: 'umd' })) .pipe(gulp.dest('scripts/')) }); diff --git a/scripts/rateit.js b/scripts/rateit.js new file mode 100644 index 0000000..79adb4a --- /dev/null +++ b/scripts/rateit.js @@ -0,0 +1,438 @@ +(function (factory) { + if (typeof module === "object" && typeof module.exports === "object") { + var v = factory(require, exports); + if (v !== undefined) module.exports = v; + } + else if (typeof define === "function" && define.amd) { + define(["require", "exports"], factory); + } +})(function (require, exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var rateit = /** @class */ (function () { + function rateit() { + this.index = 0; + this.defaultOptions = { + min: 0, + max: 5, + step: 0.5, + mode: 'bg', + icon: '★', + starwidth: 16, + starheight: 16, + readonly: false, + resetable: true, + ispreset: false + }; + } + Object.defineProperty(rateit.prototype, "resetLabel", { + get: function () { return 'reset rating'; }, + enumerable: false, + configurable: true + }); + ; + Object.defineProperty(rateit.prototype, "ratingLabel", { + get: function () { return 'rating'; }, + enumerable: false, + configurable: true + }); + ; + rateit.prototype.enable = function (selector, options) { + var _this = this; + options = options || this.defaultOptions; + selector = selector || '.rateit'; + var items = document.querySelectorAll(selector); + var _loop_1 = function () { + var item = items[i]; + //let dataSet = new DataSetWrapper(item); + var dataset = rateit.getDataSetWrapper(item); + if (dataset.getString('init')) { + this_1.redraw(item); + return "continue"; + } + item.classList.add('rateit'); + var ltr = window.getComputedStyle(item).direction === 'ltr'; + dataset.set('mode', dataset.getString('mode') || options.mode); + dataset.set('icon', dataset.getString('icon') || options.icon); + dataset.set('min', isNaN(dataset.getNumber('min')) ? options.min : dataset.getNumber('min')); //since 0 is a valid entry, we check for isNaN + dataset.set('max', isNaN(dataset.getNumber('max')) ? options.max : dataset.getNumber('max')); //since 0 is a valid entry, we check for isNaN + dataset.set('step', dataset.getNumber('step') || options.step); + dataset.set('resetable', dataset.getString('resetable') !== undefined ? dataset.getBoolean('resetable') : options.resetable); + dataset.set('readonly', dataset.getString('readonly') !== undefined ? dataset.getBoolean('readonly') : options.readonly); + dataset.set('ispreset', dataset.getString('ispreset') !== undefined ? dataset.getBoolean('ispreset') : options.ispreset); + dataset.set('backingfld', dataset.getString('backingfld') || options.backingfld); + dataset.set('starwidth', dataset.getNumber('starwidth') || options.starwidth); + dataset.set('starheight', dataset.getNumber('starheight') || options.starheight); + dataset.set('value', Math.max(dataset.getNumber('min'), Math.min(dataset.getNumber('max'), (!isNaN(dataset.getNumber('value')) ? dataset.getNumber('value') : options.value == null ? options.min : options.value)))); + if (dataset.getString('backingfld')) { + //if we have a backing field, hide it, override defaults if range or select. + var el = document.querySelector(dataset.getString('backingfld')); + var fld = el; + fld.style.display = 'none'; + if (fld.disabled || fld.readOnly) { + dataset.set('readonly', true); + } + if (el.nodeName === 'INPUT') { + if (fld.type === 'range' || fld.type === 'text') { //in browsers not support the range type, it defaults to text + dataset.set('min', parseInt(fld.getAttribute('min')) || dataset.getNumber('min')); //if we would have done fld.min it wouldn't have worked in browsers not supporting the range type. + dataset.set('max', parseInt(fld.getAttribute('max')) || dataset.getNumber('max')); + dataset.set('step', parseInt(fld.getAttribute('step')) || dataset.getNumber('step')); + } + } + if (el.nodeName == 'SELECT' && el.options.length > 1) { + var select = el; + // If backing field is a select box with valuesrc option set to "index", use the indexes of its options; otherwise, use the values. + if (select.getAttribute('data-rateit-valuesrc') === 'index') { + dataset.set('min', (!isNaN(dataset.getNumber('min')) ? dataset.getNumber('min') : 0)); + dataset.set('max', select.length - 1); + dataset.set('step', 1); + } + else { + dataset.set('min', (!isNaN(dataset.getNumber('min')) ? dataset.getNumber('min') : 0)); + dataset.set('max', Number(select.options.item(select.length - 1).value)); + dataset.set('step', Number(select.options.item(1).value) - Number(select.options.item(0).value)); + } + //see if we have a option that as explicity been selected + selectedOption = select.querySelector('option[selected]'); + if (selectedOption != null) { + // If backing field is a select box with valuesrc option set to "index", use the index of selected option; otherwise, use the value. + if (select.getAttribute('data-rateit-valuesrc') === 'index') { + dataset.set('value', selectedOption.index); + } + else { + dataset.set('value', selectedOption.value); + } + } + } + else { + //if it is not a select box, we can get's it's value using the val function. + //If it is a selectbox, we always get a value (the first one of the list), even if it was not explicity set. + dataset.set('value', fld.value); + } + } + //Create the necessary tags. For ARIA purposes we need to give the items an ID. So we use an internal index to create unique ids + var element = item.nodeName === 'DIV' ? 'div' : 'span'; + this_1.index++; + html = "<").concat(element, " id=\"rateit-range-").concat(this_1.index, "\" class=\"rateit-range\" tabindex=\"0\" role=\"slider\" aria-label=\"").concat(this_1.ratingLabel, "\" aria-owns=\"rateit-reset-").concat(this_1.index, "\" aria-valuemin=\"").concat(dataset.getNumber('min'), "\" aria-valuemax=\"").concat(dataset.getNumber('max'), "\" aria-valuenow=\"").concat(dataset.getNumber('value'), "\"><").concat(element, " class=\"rateit-empty\"><").concat(element, " class=\"rateit-selected\"><").concat(element, " class=\"rateit-hover\">"); + item.insertAdjacentHTML('beforeend', html); + if (!ltr) { + item.querySelector('.rateit-reset').style.cssFloat = 'right'; + item.querySelector('.rateit-selected').classList.add('rateit-selected-rtl'); + item.querySelector('.rateit-hover').classList.add('rateit-hover-rtl'); + } + //setup the reset button + var resetbtn = item.querySelector('.rateit-reset'); + resetbtn.addEventListener('click', function (ev) { + resetbtn.blur(); + var beforeResetEvent = new Event('beforereset'); + //if the beforereset event was cancelled then do not continue resetting + if (!item.dispatchEvent(beforeResetEvent)) { + return false; + } + var initialOptions = JSON.parse(dataset.getString('init')); + if (initialOptions.ispreset) { + _this.options(item, { ispreset: true, value: initialOptions.value }); + } + else { + _this.value(item, null); + } + item.dispatchEvent(new Event('reset')); + return false; + }, false); + //setup other events + var range = item.querySelector('.rateit-range'); + if (!dataset.getBoolean('readonly')) { + range.addEventListener('touchmove', rateit.touchHandler, false); + range.addEventListener('touchend', rateit.touchHandler, false); + range.addEventListener('mousemove', function (e) { + var score = _this.calcRawScore(item, e); + _this.setHover(item, score); + }); + //when the mouse leaves the range, we have to hide the hover stars, and show the current value. + range.addEventListener('mouseleave', function (e) { + var rateitHover = (range.querySelector('.rateit-hover')); + rateitHover.style.display = 'none'; + rateitHover.style.width = '0'; + rateitHover.dataset['width'] = ''; + item.dispatchEvent(new CustomEvent('hover')); + item.dispatchEvent(new CustomEvent('over')); + (range.querySelector('.rateit-selected')).style.display = 'block'; + }); + //when we click on the range, we have to set the value, hide the hover. + range.addEventListener('mouseup', function (e) { + var score = _this.calcRawScore(item, e); + var value = (score * dataset.getNumber('step')) + dataset.getNumber('min'); + _this.value(item, value, true); + range.blur(); + }); + //support key nav + range.addEventListener('keyup', function (e) { + if (e.which == 38 || e.which == (ltr ? 39 : 37)) { + var value = Math.min(dataset.getNumber('value') + dataset.getNumber('step'), dataset.getNumber('max')); + _this.value(item, value, true); + } + if (e.which == 40 || e.which == (ltr ? 37 : 39)) { + var value = Math.max(dataset.getNumber('value') - dataset.getNumber('step'), dataset.getNumber('min')); + _this.value(item, value, true); + } + }); + } + dataset.set('init', JSON.stringify(this_1.options(item))); //cheap way to create a clone + this_1.redraw(item); + }; + var this_1 = this, selectedOption, html; + for (var i = 0; i < items.length; i++) { + _loop_1(); + } + }; + rateit.prototype.options = function (selectorOrElement, options) { + var element = rateit.getElementFromSelectorOrElement(selectorOrElement); + var dataset = rateit.getDataSetWrapper(element); + if (typeof (options) === 'undefined') { + var numberProps = ['value', 'min', 'max', 'step', 'starwidth', 'starheight']; + var booleanProps = ['readonly', 'ispreset', 'resetable']; + var opts = {}; + for (var property in element.dataset) { + if (element.dataset.hasOwnProperty(property) && property !== 'rateitInit') { + var key = property.substring('rateit'.length).toLowerCase(); + var raw = element.dataset[property]; + if (numberProps.indexOf(key) !== -1) { + opts[key] = Number(raw); + } + else if (booleanProps.indexOf(key) !== -1) { + opts[key] = raw === 'true'; + } + else { + opts[key] = raw; + } + } + } + return opts; + } + if (options.hasOwnProperty('value')) { + //if we also got the value passed in, call the value method, since that takes also care of setting the correct value in the backingfield. + this.value(element, options.value, false); + } + for (var property in options) { + if (options.hasOwnProperty(property) && property !== 'init') { + dataset.set(property, options[property]); + } + } + this.redraw(element); + }; + rateit.prototype.value = function (selectorOrElement, value, raiseEvents) { + var element = rateit.getElementFromSelectorOrElement(selectorOrElement); + var dataset = rateit.getDataSetWrapper(element); + if (typeof (value) === 'undefined') + return dataset.getNumber('value'); + if (value === null) { + value = dataset.getNumber('min'); + } + var range = element.querySelector('.rateit-range'); + var rangeComputedStyle = window.getComputedStyle(range); + var presetclass = "rateit-preset".concat((rangeComputedStyle.direction === 'ltr') ? '' : '-rtl'); + if (raiseEvents === true) { + var event_1 = new CustomEvent('beforerated', { detail: { value: value } }); // has to be CustomEvent + if (!element.dispatchEvent(event_1)) { + return false; + } + } + dataset.set('value', value); + if (dataset.getString('backingfld') != null) { + // If backing field is a select box with valuesrc option set to "index", update its selectedIndex property; otherwise, update its value. + var backingFld = document.querySelector(dataset.getString('backingfld')); + if (backingFld.nodeName == 'SELECT' && backingFld.getAttribute('data-rateit-valuesrc') === 'index') { + backingFld.selectedIndex = value; + } + else { + backingFld.value = value.toString(); + } + if (raiseEvents === true) { + var evt = new Event('change', { bubbles: true, cancelable: false }); + backingFld.dispatchEvent(evt); + } + } + if (dataset.getBoolean('ispreset')) { //if it was a preset value, unset that. + (range.querySelector('.rateit-selected')).classList.remove(presetclass); + dataset.set('ispreset', false); + } + (range.querySelector('.rateit-hover')).style.display = 'none'; + var rateItSelected = (range.querySelector('.rateit-selected')); + rateItSelected.style.width = "".concat(value * dataset.getNumber('starwidth') - (dataset.getNumber('min') * dataset.getNumber('starwidth')), "px"); + rateItSelected.style.display = 'block'; + if (raiseEvents === true) { + element.dispatchEvent(new CustomEvent('hover')); + element.dispatchEvent(new CustomEvent('over')); + element.dispatchEvent(new CustomEvent('rated', { detail: { value: value } })); + } + return true; + }; + rateit.prototype.revert = function (selectorOrElement) { + var element = rateit.getElementFromSelectorOrElement(selectorOrElement); + var dataset = rateit.getDataSetWrapper(element); + this.options(selectorOrElement, JSON.parse(dataset.getString('init'))); + }; + rateit.prototype.setHover = function (element, score) { + var dataset = rateit.getDataSetWrapper(element); + var range = element.querySelector('.rateit-range'); + var w = score * dataset.getNumber('starwidth') * dataset.getNumber('step'); + var h = range.querySelector('.rateit-hover'); + if (h.dataset['width'] != w.toString()) { + (range.querySelector('.rateit-selected')).style.display = 'none'; + h.style.width = "".concat(w, "px"); + h.style.display = 'block'; + h.dataset['width'] = w.toString(); + var value = [(score * dataset.getNumber('step')) + dataset.getNumber('min')]; + var ev = new CustomEvent('hover', { + detail: { + value: (score * dataset.getNumber('step')) + dataset.getNumber('min') + } + }); + element.dispatchEvent(new CustomEvent('hover', { detail: { value: value } })); + element.dispatchEvent(new CustomEvent('over', { detail: { value: value } })); + } + }; + rateit.prototype.calcRawScore = function (el, ev) { + var pageX = (ev.type.substring(0, 5) === 'touch') + ? ev.changedTouches[0].pageX + : ev.pageX; + var dataset = rateit.getDataSetWrapper(el); + var range = el.querySelector('.rateit-range'); + var rangeComputedStyle = window.getComputedStyle(range); + var offsetLeft = range.getBoundingClientRect().left + document.body.scrollLeft; + var rangeWidth = parseInt(rangeComputedStyle.width); + var offsetx = pageX - offsetLeft; + if (rangeComputedStyle.direction === 'rtl') { + offsetx = parseInt(rangeComputedStyle.width) - offsetx; + } + ; + if (offsetx > rangeWidth) { + offsetx = rangeWidth; + } + if (offsetx < 0) { + offsetx = 0; + } + return Math.ceil(offsetx / dataset.getNumber('starwidth') * (1 / dataset.getNumber('step'))); + }; + rateit.prototype.redraw = function (element) { + var dataset = rateit.getDataSetWrapper(element); + var isfont = dataset.getString('mode') === 'font'; + var range = element.querySelector('.rateit-range'); + var rangeComputedStyle = window.getComputedStyle(range); + var ltr = rangeComputedStyle.direction === 'ltr'; + if (isfont) { + element.classList.add('rateit-font'); + element.classList.remove('rateit-bg'); + //fill the ranges with the icons + var icon = dataset.getString('icon'); + var stars = dataset.getNumber('max') - dataset.getNumber('min'); + var txt_1 = ''; + for (var i = 0; i < stars; i++) { + txt_1 += icon; + } + Array.prototype.forEach.call(range.children, function (item) { + item.innerText = txt_1; + }); + dataset.set('starwidth', parseInt(rangeComputedStyle.width) / (dataset.getNumber('max') - dataset.getNumber('min'))); + range.style.width = ''; + range.style.height = ''; + Array.prototype.forEach.call(element.querySelectorAll('.rateit-selected, .rateit-hover'), function (item) { + item.style.height = ''; + }); + } + else { + element.classList.add('rateit-bg'); + element.classList.remove('rateit-font'); + Array.prototype.forEach.call(range.children, function (item) { + item.innerText = ''; + }); + //set the range element to fit all the stars. + range.style.width = "".concat(dataset.getNumber('starwidth') * (dataset.getNumber('max') - dataset.getNumber('min')), "px"); + range.style.height = "".concat(dataset.getNumber('starheight'), "px"); + Array.prototype.forEach.call(element.querySelectorAll('.rateit-selected, .rateit-hover'), function (item) { + item.style.height = "".concat(dataset.getNumber('starheight'), "px"); + }); + } + //add/remove the preset class + var presetclass = "rateit-preset".concat((ltr) ? '' : '-rtl'); + if (dataset.getBoolean('ispreset')) { + element.querySelector('.rateit-selected').classList.add(presetclass); + } + else { + element.querySelector('.rateit-selected').classList.remove(presetclass); + } + //set the value if we have it. + if (dataset.getNumber('value') != null) { + var score = (dataset.getNumber('value') - dataset.getNumber('min')) * dataset.getNumber('starwidth'); + (element.querySelector('.rateit-selected')).style.width = "".concat(score, "px"); + } + //sets the hover element based on the score. + var resetbtn = element.querySelector('.rateit-reset'); + if (!dataset.getBoolean('readonly')) { + //if we are not read only, add all the events + //if we have a reset button, set the event handler. + if (!dataset.getBoolean('resetable')) { + resetbtn.style.display = 'none'; + } + if (dataset.getBoolean('resetable')) { + resetbtn.style.display = 'block'; + } + } + else { + resetbtn.style.display = 'none'; + } + range.setAttribute('aria-readonly', dataset.getString('readonly')); + }; + rateit.getElementFromSelectorOrElement = function (selectorOrElement) { + if (typeof (selectorOrElement) === 'string') { + return document.querySelector(selectorOrElement); + } + else { + return selectorOrElement; + } + }; + //touch converter http://ross.posterous.com/2008/08/19/iphone-touch-events-in-javascript/ + rateit.touchHandler = function (event) { + var touches = event.changedTouches, first = touches[0], type = ""; + switch (event.type) { + case "touchmove": + type = "mousemove"; + break; + case "touchend": + type = "mouseup"; + break; + default: return; + } + var simulatedEvent = document.createEvent("MouseEvent"); + simulatedEvent.initMouseEvent(type, true, true, window, 1, first.screenX, first.screenY, first.clientX, first.clientY, false, false, false, false, 0 /*left*/, null); + first.target.dispatchEvent(simulatedEvent); + event.preventDefault(); + }; + rateit.getDataSetWrapper = function (item) { + var createKey = function (key) { + return 'rateit' + key.charAt(0).toUpperCase() + key.substr(1); + }; + return { + getNumber: function (key) { return Number(item.dataset[createKey(key)]); }, + getBoolean: function (key) { return item.dataset[createKey(key)] === 'true'; }, + getString: function (key) { return item.dataset[createKey(key)]; }, + set: function (key, value) { + if (value == null) { + delete item.dataset[createKey(key)]; //might not work in safari http://stackoverflow.com/questions/9200712/how-to-remove-data-attributes-using-html5-dataset + } + else { + item.dataset[createKey(key)] = value.toString(); + } + } + }; + }; + return rateit; + }()); + exports.default = rateit; + if (typeof document !== 'undefined') { + document.addEventListener('DOMContentLoaded', function () { + var _rateit = new rateit(); + _rateit.enable(); + }); + } +}); diff --git a/src/rateit.ts b/src/rateit.ts index 28de923..f7b90bd 100644 --- a/src/rateit.ts +++ b/src/rateit.ts @@ -53,6 +53,11 @@ export default class rateit { //let dataSet = new DataSetWrapper(item); let dataset: DataSetWrapper = rateit.getDataSetWrapper(item); + if (dataset.getString('init')) { + this.redraw(item); + continue; + } + item.classList.add('rateit'); let ltr = window.getComputedStyle(item).direction === 'ltr'; @@ -162,43 +167,45 @@ export default class rateit { //setup other events const range = item.querySelector('.rateit-range'); - range.addEventListener('touchmove', rateit.touchHandler, false); - range.addEventListener('touchend', rateit.touchHandler, false); - range.addEventListener('mousemove', (e: MouseEvent) => { - var score = this.calcRawScore(item, e); - this.setHover(item, score); - }); - - //when the mouse leaves the range, we have to hide the hover stars, and show the current value. - range.addEventListener('mouseleave', (e: MouseEvent) => { - const rateitHover = ((range.querySelector('.rateit-hover'))); - rateitHover.style.display = 'none'; - rateitHover.style.width = '0'; - rateitHover.dataset['width'] = '';; - item.dispatchEvent(new CustomEvent('hover')); - item.dispatchEvent(new CustomEvent('over')); - - ((range.querySelector('.rateit-selected'))).style.display = 'block'; - }); - //when we click on the range, we have to set the value, hide the hover. - range.addEventListener('mouseup', (e: MouseEvent) => { - var score = this.calcRawScore(item, e); - var value = (score * dataset.getNumber('step')) + dataset.getNumber('min'); - this.value(item, value, true); - range.blur(); - }); - - //support key nav - range.addEventListener('keyup', (e: KeyboardEvent) => { - if (e.which == 38 || e.which == (ltr ? 39 : 37)) { - const value = Math.min(dataset.getNumber('value') + dataset.getNumber('step'), dataset.getNumber('max')); + if (!dataset.getBoolean('readonly')) { + range.addEventListener('touchmove', rateit.touchHandler, false); + range.addEventListener('touchend', rateit.touchHandler, false); + range.addEventListener('mousemove', (e: MouseEvent) => { + var score = this.calcRawScore(item, e); + this.setHover(item, score); + }); + + //when the mouse leaves the range, we have to hide the hover stars, and show the current value. + range.addEventListener('mouseleave', (e: MouseEvent) => { + const rateitHover = ((range.querySelector('.rateit-hover'))); + rateitHover.style.display = 'none'; + rateitHover.style.width = '0'; + rateitHover.dataset['width'] = ''; + item.dispatchEvent(new CustomEvent('hover')); + item.dispatchEvent(new CustomEvent('over')); + + ((range.querySelector('.rateit-selected'))).style.display = 'block'; + }); + //when we click on the range, we have to set the value, hide the hover. + range.addEventListener('mouseup', (e: MouseEvent) => { + var score = this.calcRawScore(item, e); + var value = (score * dataset.getNumber('step')) + dataset.getNumber('min'); this.value(item, value, true); - } - if (e.which == 40 || e.which == (ltr ? 37 : 39)) { - const value = Math.max(dataset.getNumber('value') - dataset.getNumber('step'), dataset.getNumber('min')); - this.value(element, value, true); - } - }); + range.blur(); + }); + + //support key nav + range.addEventListener('keyup', (e: KeyboardEvent) => { + if (e.which == 38 || e.which == (ltr ? 39 : 37)) { + const value = Math.min(dataset.getNumber('value') + dataset.getNumber('step'), dataset.getNumber('max')); + this.value(item, value, true); + } + if (e.which == 40 || e.which == (ltr ? 37 : 39)) { + const value = Math.max(dataset.getNumber('value') - dataset.getNumber('step'), dataset.getNumber('min')); + this.value(item, value, true); + } + }); + } dataset.set('init', JSON.stringify(this.options(item)));//cheap way to create a clone @@ -215,10 +222,20 @@ export default class rateit { const element: HTMLElement = rateit.getElementFromSelectorOrElement(selectorOrElement); const dataset = rateit.getDataSetWrapper(element); if (typeof (options) === 'undefined') { + const numberProps = ['value', 'min', 'max', 'step', 'starwidth', 'starheight']; + const booleanProps = ['readonly', 'ispreset', 'resetable']; let opts: any = {}; for (var property in element.dataset) { if (element.dataset.hasOwnProperty(property) && property !== 'rateitInit') { - opts[property.substring('rateit'.length).toLowerCase()] = element.dataset[property]; + const key = property.substring('rateit'.length).toLowerCase(); + const raw = element.dataset[property]; + if (numberProps.indexOf(key) !== -1) { + opts[key] = Number(raw); + } else if (booleanProps.indexOf(key) !== -1) { + opts[key] = raw === 'true'; + } else { + opts[key] = raw; + } } } return opts; @@ -243,17 +260,21 @@ export default class rateit { public value(element: HTMLElement): Number public value(selector: HTMLElement): Number - public value(element: HTMLElement, value: number): boolean - public value(selector: string, value: number): boolean - public value(element: HTMLElement, value: number, raiseEvents: boolean): boolean - public value(selector: string, value: number, raiseEvents: boolean): boolean - public value(selectorOrElement: any, value?: number, raiseEvents?: boolean): any { + public value(element: HTMLElement, value: number | null): boolean + public value(selector: string, value: number | null): boolean + public value(element: HTMLElement, value: number | null, raiseEvents: boolean): boolean + public value(selector: string, value: number | null, raiseEvents: boolean): boolean + public value(selectorOrElement: any, value?: number | null, raiseEvents?: boolean): any { const element: HTMLElement = rateit.getElementFromSelectorOrElement(selectorOrElement); const dataset: DataSetWrapper = rateit.getDataSetWrapper(element); if (typeof (value) === 'undefined') return dataset.getNumber('value'); + if (value === null) { + value = dataset.getNumber('min'); + } + const range = element.querySelector('.rateit-range'); const rangeComputedStyle = window.getComputedStyle(range); const presetclass = `rateit-preset${(rangeComputedStyle.direction === 'ltr') ? '' : '-rtl'}`; @@ -340,7 +361,7 @@ export default class rateit { } private calcRawScore(el: HTMLElement, ev: UIEvent): number { - const pageX: number = (event.type.substring(0, 5) === 'touch') + const pageX: number = (ev.type.substring(0, 5) === 'touch') ? (ev).changedTouches[0].pageX : (ev).pageX; const dataset = rateit.getDataSetWrapper(el); @@ -510,5 +531,9 @@ interface DataSetWrapper { set(key: RateItItemDataTypes, value: any): void; } - - +if (typeof document !== 'undefined') { + document.addEventListener('DOMContentLoaded', function () { + var _rateit = new rateit(); + _rateit.enable(); + }); +} diff --git a/tsconfig.json b/tsconfig.json index da04162..200d20d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "module": "commonjs", + "module": "umd", "noImplicitAny": true, "removeComments": true, @@ -11,6 +11,6 @@ }, "compileOnSave": true, "files": [ - "./scripts/rateit.ts" + "./src/rateit.ts" ] } \ No newline at end of file