From c4633bcc1aa8f8782603cb6bb6fbbb7db34ff58d Mon Sep 17 00:00:00 2001 From: Aleksuei Riabtsev Date: Wed, 17 Jan 2018 10:09:32 -0500 Subject: [PATCH 1/3] move observables to common bus - ensure chart-slider always has an updated reference to highchart object after the chart is refreshed Closes #9 --- package.json | 5 +-- poi.config.js | 7 ++-- src/api/main.ts | 6 ++-- src/classes/chart.ts | 51 ++++++++++++++-------------- src/classes/section.ts | 5 +-- src/components/chart-slider.vue | 59 ++++++++++++++++++++++----------- src/components/chart-table.vue | 41 +++++++++++++++-------- src/components/chart.vue | 39 +++++++++++++++++----- src/observable-bus.ts | 52 +++++++++++++++++++++++++++-- src/store/main.ts | 23 ++++++++----- 10 files changed, 196 insertions(+), 92 deletions(-) diff --git a/package.json b/package.json index 790160d..d4dc491 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dqvue", - "version": "0.4.0", + "version": "0.4.1", "description": "", "main": "./dist/dqvue.js", "types": "./index.d.ts", @@ -42,8 +42,5 @@ "webpack-bundle-analyzer": "^2.9.2", "wnumb": "^1.1.0", "yargs": "9.0.1" - }, - "dependencies": { - "vue-template-compiler": "^2.5.13" } } diff --git a/poi.config.js b/poi.config.js index d1f535d..64de23d 100644 --- a/poi.config.js +++ b/poi.config.js @@ -28,9 +28,7 @@ module.exports = { }) ], extendWebpack(config) { - config.resolve.alias - .set('vue$', 'vue/dist/vue.esm.js') // vue.esm include template compiler; without it all templates need to be pre-compiled - .set('highcharts', 'highcharts/highcharts.src.js'); // include non-minified highcharts into the dev build + config.resolve.alias.set('vue$', 'vue/dist/vue.esm.js'); // vue.esm include template compiler; without it all templates need to be pre-compiled config.output.set('library', 'DQV').set('libraryExport', 'default'); // exposes the default export directly on the global library variable: https://webpack.js.org/configuration/output/#output-libraryexport @@ -40,6 +38,7 @@ module.exports = { karma: { mime: { 'text/x-typescript': ['ts'] - } + }, + karmaTypescriptConfig: undefined } }; diff --git a/src/api/main.ts b/src/api/main.ts index 5cb8e61..1429288 100644 --- a/src/api/main.ts +++ b/src/api/main.ts @@ -6,7 +6,7 @@ import { DVSection } from './../classes/section'; import { DVChart } from './../classes/chart'; import { sections, charts } from './../store/main'; -import { sectionCreatedSubject, chartCreatedSubject } from './../observable-bus'; +import { sectionCreated, chartCreated } from './../observable-bus'; import { Observable } from 'rxjs/Observable'; @@ -73,8 +73,8 @@ export default { Section: DVSection, Chart: DVChart, - sectionCreated: sectionCreatedSubject.asObservable(), - chartCreated: chartCreatedSubject.asObservable(), + sectionCreated: sectionCreated.asObservable(), + chartCreated: chartCreated.asObservable(), get sections(): { [name: string]: DVSection } { // TODO: return a clone instead of original object so users can't mess with it diff --git a/src/classes/chart.ts b/src/classes/chart.ts index a061579..d3ee95f 100644 --- a/src/classes/chart.ts +++ b/src/classes/chart.ts @@ -7,10 +7,18 @@ import 'rxjs/add/operator/filter'; import { DVHighcharts } from './../api/main'; -import { chartCreatedSubject } from './../observable-bus'; +import { + chartCreated, + chartRendered, + chartViewData, + chartConfigUpdated, + ChartEvent, + ChartRenderedEvent, + ChartViewDataEvent +} from './../observable-bus'; import { isPromise } from './../utils'; -import Chart, { RenderedEvent, ViewDataEvent } from './../components/chart.vue'; +import { DVSection } from './section'; const log: loglevel.Logger = loglevel.getLogger('dv-chart'); @@ -38,18 +46,6 @@ export class DVChart { private _highchartObject: DVHighcharts.ChartObject | null = null; - private _renderedSubject: Subject = new Subject< - DVHighcharts.ChartObject - >(); - get rendered(): Observable { - return this._renderedSubject.asObservable(); - } - - private _configUpdatedSubject: Subject = new Subject(); - get configUpdated(): Observable { - return this._configUpdatedSubject.asObservable(); - } - constructor({ id = uniqid.time(), config = null, data = null }: DVChartOptions = {}) { this.id = id; @@ -69,20 +65,17 @@ export class DVChart { // TODO: merge observable from the chart view to the rendered observable here // every time the chart is re-rendered, store the reference to the highchart object - Chart.rendered - .filter(event => event.chartId === this.id) - .subscribe((event: RenderedEvent) => { - this._highchartObject = event.highchartObject; - this._renderedSubject.next(this._highchartObject); - }); - - Chart.viewData - .filter((event: ViewDataEvent) => { - return event.chartId === this.id; - }) + chartRendered + .filter(this._filterStream, this) + .subscribe( + (event: ChartRenderedEvent) => (this._highchartObject = event.highchartObject) + ); + + chartViewData + .filter(this._filterStream, this) .subscribe(() => (this._isTableGenerated = true)); - chartCreatedSubject.next(this); + chartCreated.next({ chartId: this.id, dvchart: this }); } get isTableGenerated(): boolean { @@ -212,7 +205,7 @@ export class DVChart { log.info(`[chart='${this.id}'] chart config is valid`); this._isConfigValid = true; - this._configUpdatedSubject.next(this); + chartConfigUpdated.next({ chartId: this.id, dvchart: this }); } get isConfigValid(): boolean { @@ -228,4 +221,8 @@ export class DVChart { return this; } */ + + private _filterStream(event: ChartEvent): boolean { + return event.chartId === this.id; + } } diff --git a/src/classes/section.ts b/src/classes/section.ts index 209c6f8..e686775 100644 --- a/src/classes/section.ts +++ b/src/classes/section.ts @@ -6,7 +6,7 @@ import { DVChart } from './chart'; import Chart from './../components/chart.vue'; import ChartTable from './../components/chart-table.vue'; -import { sectionCreatedSubject } from './../observable-bus'; +import { sectionCreated } from './../observable-bus'; import { isPromise, isFunction, isString, isObject } from './../utils'; // TODO: constrain logging to 'warn' in production builds @@ -60,7 +60,7 @@ export class DVSection { } // push the new section through the stream - sectionCreatedSubject.next(this); + sectionCreated.next({ sectionId: this.id, dvsection: this }); if (mount) { this._mount = mount; @@ -287,6 +287,7 @@ export class DVSection { } // TODO: when dismouning a section, remove all the chart tables originating from charts in this section, even ones that are in different sections + // TODO: maybe instead of removing all the chart tables in other sections, just hide them and reactivate them if the section with their parent chart is remounted this._vm.$destroy(); this._isMounted = false; diff --git a/src/components/chart-slider.vue b/src/components/chart-slider.vue index fa270e3..c303ecc 100644 --- a/src/components/chart-slider.vue +++ b/src/components/chart-slider.vue @@ -9,7 +9,7 @@ - + diff --git a/src/components/chart-table.vue b/src/components/chart-table.vue index fc34adb..bb247bc 100644 --- a/src/components/chart-table.vue +++ b/src/components/chart-table.vue @@ -10,7 +10,15 @@ import uniqid from 'uniqid'; import loglevel from 'loglevel'; import api from './../api/main'; -import Chart, { RenderedEvent, ViewDataEvent } from './../components/chart.vue'; +import { + chartRendered, + chartViewData, + ChartEvent, + ChartRenderedEvent, + ChartViewDataEvent +} from './../observable-bus'; + +//import Chart, { RenderedEvent, ViewDataEvent } from './../components/chart.vue'; import { charts } from './../store/main'; import 'rxjs/add/operator/filter'; @@ -48,15 +56,17 @@ export default class ChartTable extends Vue { created(): void { if (!this.chartId) { log.error( - `[chart-table='${this.id}' section='${this - .rootSectionId}'] table cannot be linked because it is missing a parent chart id` + `[chart-table='${this.id}' section='${ + this.rootSectionId + }'] table cannot be linked because it is missing a parent chart id` ); } if (!charts[this.chartId]) { log.error( - `[chart-table='${this.id}' chart='${this.chartId}' section='${this - .rootSectionId}'] referenced chart does not exist` + `[chart-table='${this.id}' chart='${this.chartId}' section='${ + this.rootSectionId + }'] referenced chart does not exist` ); return; } @@ -88,25 +98,28 @@ export default class ChartTable extends Vue { // --- TODO: deprecated; should be removed when `dv-auto-render` attribute is removed if (this.autoRender) { - Chart.rendered + chartRendered.filter(this._filterStream, this).subscribe(this.generateTable); + /* Chart.rendered .filter((event: RenderedEvent) => event.chartId === this.chartId) - .subscribe(() => this.generateTable()); + .subscribe(() => this.generateTable()); */ } // --- - Chart.viewData + chartViewData.filter(this._filterStream, this).subscribe(this.generateTable); + /* Chart.viewData .filter((event: ViewDataEvent) => { return event.chartId === this.chartId; }) - .subscribe(() => this.generateTable()); + .subscribe(() => this.generateTable()); */ } generateTable(): void { // render table can only be called after the chart has been rendered if (!this.dvchart.highchart) { log.warn( - `[chart-table='${this.id}' chart='${this - .chartId}'] something's wrong - trying to render the table before chart is ready` + `[chart-table='${this.id}' chart='${ + this.chartId + }'] something's wrong - trying to render the table before chart is ready` ); return; } @@ -126,11 +139,13 @@ export default class ChartTable extends Vue { this.highchartsDataTable ); } + + private _filterStream(event: ChartEvent): boolean { + return event.chartId === this.chartId; + } } - - diff --git a/src/components/chart.vue b/src/components/chart.vue index d9fac37..d0dd299 100644 --- a/src/components/chart.vue +++ b/src/components/chart.vue @@ -15,8 +15,17 @@ import { Subject } from 'rxjs/Subject'; import 'rxjs/add/operator/filter'; import api, { DVHighcharts } from './../api/main'; +import { + chartRendered, + chartConfigUpdated, + chartViewData, + chartSetExtremes, + ChartConfigUpdatedEvent +} from './../observable-bus'; + import { DVChart } from './../classes/chart'; import { charts } from './../store/main'; + import ChartSlider from './../components/chart-slider.vue'; const log: loglevel.Logger = loglevel.getLogger('dv-chart'); @@ -44,14 +53,14 @@ interface EnhancedExportingOptions extends Highcharts.ExportingOptions { } }) export default class Chart extends Vue { - private static _rendered: Subject = new Subject(); + /* private static _rendered: Subject = new Subject(); static rendered = Chart._rendered.asObservable(); private static _viewData: Subject = new Subject(); static viewData = Chart._viewData.asObservable(); private static _setExtremes: Subject = new Subject(); - static setExtremes = Chart._setExtremes.asObservable(); + static setExtremes = Chart._setExtremes.asObservable(); */ dvchart: DVChart; isLoading: boolean = true; @@ -109,7 +118,8 @@ export default class Chart extends Vue { return; } - Chart._viewData.next({ chartId: this.id }); + // Chart._viewData.next({ chartId: this.id }); + chartViewData.next({ chartId: this.id, dvchart: this.dvchart }); } /** @@ -133,8 +143,8 @@ export default class Chart extends Vue { // find chart and table containers this._chartContainer = this.$el.querySelector(DV_CHART_CONTAINER_ELEMENT) as HTMLElement; - this.dvchart.configUpdated - .filter(dvchart => dvchart.id === this.id) + chartConfigUpdated + .filter((event: ChartConfigUpdatedEvent) => event.chartId === this.id) .subscribe(() => this.renderChart()); if (this.dvchart.isConfigValid) { @@ -163,12 +173,19 @@ export default class Chart extends Vue { { events: { setExtremes: (event: { max: number; min: number }) => { - Chart._setExtremes.next({ + chartSetExtremes.next({ chartId: this.id, + dvchart: this.dvchart, axis: 'xAxis', max: event.max, min: event.min }); + /* Chart._setExtremes.next({ + chartId: this.id, + axis: 'xAxis', + max: event.max, + min: event.min + }); */ } } } as Object, @@ -180,11 +197,17 @@ export default class Chart extends Vue { .config as Highcharts.Options); this.isLoading = false; - Chart._rendered.next({ + chartRendered.next({ chartId: this.id, + dvchart: this.dvchart, highchartObject: this.highchartObject as DVHighcharts.ChartObject }); + /* Chart._rendered.next({ + chartId: this.id, + highchartObject: this.highchartObject as DVHighcharts.ChartObject + }); */ + if (this.autoGenerateTable) { this.simulateViewDataClick(); } @@ -194,4 +217,4 @@ export default class Chart extends Vue { \ No newline at end of file + diff --git a/src/observable-bus.ts b/src/observable-bus.ts index 6e9cfec..b976613 100644 --- a/src/observable-bus.ts +++ b/src/observable-bus.ts @@ -1,9 +1,55 @@ import { Subject } from 'rxjs/Subject'; +import { DVHighcharts } from './api/main'; + import { DVSection } from './classes/section'; import { DVChart } from './classes/chart'; -const sectionCreatedSubject: Subject = new Subject(); -const chartCreatedSubject: Subject = new Subject(); +// #region chart events + +export interface ChartEvent { + chartId: string; + dvchart: DVChart; +} + +export interface ChartCreatedEvent extends ChartEvent {} + +export interface ChartRenderedEvent extends ChartEvent { + highchartObject: DVHighcharts.ChartObject; +} + +export interface ChartConfigUpdatedEvent extends ChartEvent {} + +export interface ChartViewDataEvent extends ChartEvent {} + +export interface ChartSetExtremesEvent extends ChartEvent { + axis: 'xAxis' | 'yAxis'; + max: number; + min: number; +} + +export const chartCreated: Subject = new Subject(); +export const chartRendered: Subject = new Subject(); +export const chartConfigUpdated: Subject = new Subject< + ChartConfigUpdatedEvent +>(); +export const chartViewData: Subject = new Subject(); +export const chartSetExtremes: Subject = new Subject< + ChartSetExtremesEvent +>(); + +// #endregion + +// #region section events + +export interface SectionEvent { + sectionId: string; +} + +export interface SectionCreatedEvent extends SectionEvent { + dvsection: DVSection; +} + +export const sectionCreated: Subject = new Subject(); -export { sectionCreatedSubject, chartCreatedSubject }; +// #endregion diff --git a/src/store/main.ts b/src/store/main.ts index 9e08385..548b0df 100644 --- a/src/store/main.ts +++ b/src/store/main.ts @@ -1,7 +1,12 @@ import { DVSection } from './../classes/section'; import { DVChart } from './../classes/chart'; -import { sectionCreatedSubject, chartCreatedSubject } from './../observable-bus'; +import { + sectionCreated, + chartCreated, + SectionCreatedEvent, + ChartCreatedEvent +} from './../observable-bus'; import { isString } from './../utils'; @@ -13,26 +18,26 @@ const charts: { [name: string]: DVChart } = {}; /** * Adds a DV Section to the reference container if another section with the same id does not exist. */ -function addSection(section: DVSection): void { - if (sections[section.id]) { +function addSection(event: SectionCreatedEvent): void { + if (sections[event.sectionId]) { return; } - sections[section.id] = section; + sections[event.sectionId] = event.dvsection; } /** * Adds a DV Chart to the reference container if another chart with the same id does not exist. */ -function addChart(chart: DVChart): void { - if (charts[chart.id]) { +function addChart(event: ChartCreatedEvent): void { + if (charts[event.chartId]) { return; } - charts[chart.id] = chart; + charts[event.chartId] = event.dvchart; } -sectionCreatedSubject.subscribe(addSection); -chartCreatedSubject.subscribe(addChart); +sectionCreated.subscribe(addSection); +chartCreated.subscribe(addChart); export { sections, charts }; From 5042411b372ce4bb12126c5bfb3e418cc2dc821a Mon Sep 17 00:00:00 2001 From: Aleksuei Riabtsev Date: Wed, 17 Jan 2018 13:46:23 -0500 Subject: [PATCH 2/3] preserve original setExtreme events - only works for a single x axis right now - updated logic of applying modifications to the config - clean up - some comments Closes #10 --- package.json | 2 +- src/api/main.ts | 11 +++ src/classes/chart.ts | 43 +++++++++--- src/components/chart-slider.vue | 32 ++++++--- src/components/chart-table.vue | 8 --- src/components/chart.vue | 120 ++++++++++++++++---------------- src/index.ejs | 10 ++- src/utils.ts | 6 +- 8 files changed, 143 insertions(+), 89 deletions(-) diff --git a/package.json b/package.json index d4dc491..1a00ab6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dqvue", - "version": "0.4.1", + "version": "0.4.2", "description": "", "main": "./dist/dqvue.js", "types": "./index.d.ts", diff --git a/src/api/main.ts b/src/api/main.ts index 1429288..a328939 100644 --- a/src/api/main.ts +++ b/src/api/main.ts @@ -32,6 +32,15 @@ function importHighcharts(): void { // semi-public functionality not exposed on the HC declarations export namespace DVHighcharts { + export interface Options extends Highcharts.Options { + exporting: ExportingOptions; + } + + export interface ExportingOptions extends Highcharts.ExportingOptions { + // `menuItemDefinitions` is not included in the `ExportingOptions` type + menuItemDefinitions: { [name: string]: MenuItem | null }; + } + export interface ChartObject extends Highcharts.ChartObject { // https://api.highcharts.com/highcharts/chart.resetZoomButton resetZoomButton: Highcharts.ElementObject | undefined; @@ -50,6 +59,8 @@ export namespace DVHighcharts { minRange: number; } + export interface AxisEvent extends Highcharts.AxisEvent {} + // https://api.highcharts.com/highcharts/exporting.menuItemDefinitions export interface MenuItem extends Highcharts.MenuItem { text: string; diff --git a/src/classes/chart.ts b/src/classes/chart.ts index d3ee95f..226ec4f 100644 --- a/src/classes/chart.ts +++ b/src/classes/chart.ts @@ -1,5 +1,6 @@ import uniqid from 'uniqid'; import loglevel from 'loglevel'; +import deepmerge from 'deepmerge'; import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; @@ -28,17 +29,28 @@ export type SeriesData = export interface DVChartOptions { id?: string; - config?: Highcharts.Options | Promise; + config?: DVHighcharts.Options | Promise; data?: SeriesData | Promise; } export class DVChart { + private static configDefaults: DVHighcharts.Options = { + xAxis: { + events: { + setExtremes: () => {} + } + }, + exporting: { + menuItemDefinitions: {} + } + }; + readonly id: string; private _isConfigValid: boolean = false; - private _config: Highcharts.Options | null = null; - private _configPromise: Promise | null = null; + private _config: DVHighcharts.Options | null = null; + private _configPromise: Promise | null = null; private _data: SeriesData | null = null; private _dataPromise: Promise | null = null; @@ -51,7 +63,7 @@ export class DVChart { log.info(`[chart='${this.id}'] new chart is created`); - if (isPromise(config)) { + if (isPromise(config)) { this.setConfig(config); } else if (config !== null) { this.config = config; @@ -90,8 +102,8 @@ export class DVChart { return this._highchartObject; } - set config(value: Highcharts.Options | null) { - this._config = value; + set config(value: DVHighcharts.Options | null) { + this._config = this._addConfigDefaults(value); this._configPromise = null; log.info(`[chart='${this.id}'] config value is set successfully`); @@ -100,7 +112,7 @@ export class DVChart { this._validateConfig(); } - setConfig(value: Promise): DVChart { + setConfig(value: Promise): DVChart { log.info(`[chart='${this.id}'] waiting for config promise to resolve`); this._configPromise = value; @@ -113,10 +125,25 @@ export class DVChart { return this; } - get config(): Highcharts.Options | null { + get config(): DVHighcharts.Options | null { return this._config; } + /** + * apply some default/empty values to the config which are used by internal compoenents + * this makes it easier to access them without have to check if they exist all the time + * + * @private + * @memberof DVChart + */ + private _addConfigDefaults(config: DVHighcharts.Options | null): DVHighcharts.Options | null { + if (!config) { + return null; + } + + return deepmerge(DVChart.configDefaults, config); + } + set data(value: SeriesData | null) { this._data = value; this._dataPromise = null; diff --git a/src/components/chart-slider.vue b/src/components/chart-slider.vue index c303ecc..d56a245 100644 --- a/src/components/chart-slider.vue +++ b/src/components/chart-slider.vue @@ -53,12 +53,15 @@ export default class ChartSlider extends Vue { @Prop() axis: ChartSetExtremesEvent['axis']; + // TODO: since the slider is always inside the chart node, pass chart id as a prop get chartId(): string { return this.rootChartId; } dvchart: DVChart | null = null; + // returns a highcharts from the current dvchart object + // this should only be called after the chart is rendered get highchart(): DVHighcharts.ChartObject { if (!this.dvchart) { throw new TypeError(`${this.logMarker} dvchart ${this.chartId} is not defined`); @@ -87,6 +90,7 @@ export default class ChartSlider extends Vue { return extremes; } + // a reference to the main slider node sliderNode: noUiSlider.Instance; minValue: number = 0; @@ -113,6 +117,7 @@ export default class ChartSlider extends Vue { chartRendered.filter(this._filterStream, this).subscribe(this.initializeSlider); // listen to extremes changed by the user + // TODO: move subscription into the init function - subscribe only if there is a slider ready chartSetExtremes.filter(this._filterStream, this).subscribe(this.setExtremesHandler); } @@ -138,15 +143,14 @@ export default class ChartSlider extends Vue { } initializeSlider(event: ChartRenderedEvent): void { - // this.highchart = event.highchartObject; - - // do not intialize the slider twice; this will happen when a chart is refreshed + // if the slider is alreayd initialize, destroy it and create a new one + // this is needed in case the slider config portion of the chart config was changed or is not compatible with the old slider configuration if (this.sliderNode) { - log.info(`${this.logMarker} slider already initialized`); - return; - } + log.info(`${this.logMarker} slider already initialized; re-creating`); - this.sliderNode = this.$el.querySelector(CHART_SLIDER_CLASS) as noUiSlider.Instance; + this.sliderNode.noUiSlider.destroy(); + this.sliderNode.classList.add('noUi-target'); + } // TODO: vet using a list of chart types that can have extremes sliders if (typeof this.extremes.dataMin === 'undefined') { @@ -181,6 +185,9 @@ export default class ChartSlider extends Vue { return; } + // cached the slider node after all checks succeed + this.sliderNode = this.$el.querySelector(CHART_SLIDER_CLASS) as noUiSlider.Instance; + noUiSlider.create( this.sliderNode, deepmerge(this.defaultSliderConfig, userSliderConfig || {}) @@ -198,7 +205,14 @@ export default class ChartSlider extends Vue { ).subscribe(this.sliderUpdateHandler); } - // + // add keyboard support to the slider + // - right/up keys move the handle to the right by a single step + // - left/down keys move the handle to the left by a single step + // - home key moves the handle all the way to the left + // - end key moves the handles all the way to the right + // - +shift holding key moves the handle by ten steps at a time + // - +ctrl holding key moves both handles at the same time + // ctrl and shift modifier keys work together all with all movement keys enableKeyboardSupport(): void { const step: number = this.sliderNode.noUiSlider.options.step || 0.01; const configMargin: number = this.sliderNode.noUiSlider.options.margin!; @@ -378,6 +392,8 @@ export default class ChartSlider extends Vue { } selfDestruct(): void { + // TODO: instead of removing the slider controls, hide them; it's possible to refresh the chart with a proper slider config + // in this can, the slider can be initialized normally this.$destroy(); while (this.$el.firstChild) { diff --git a/src/components/chart-table.vue b/src/components/chart-table.vue index bb247bc..179169d 100644 --- a/src/components/chart-table.vue +++ b/src/components/chart-table.vue @@ -99,18 +99,10 @@ export default class ChartTable extends Vue { // --- TODO: deprecated; should be removed when `dv-auto-render` attribute is removed if (this.autoRender) { chartRendered.filter(this._filterStream, this).subscribe(this.generateTable); - /* Chart.rendered - .filter((event: RenderedEvent) => event.chartId === this.chartId) - .subscribe(() => this.generateTable()); */ } // --- chartViewData.filter(this._filterStream, this).subscribe(this.generateTable); - /* Chart.viewData - .filter((event: ViewDataEvent) => { - return event.chartId === this.chartId; - }) - .subscribe(() => this.generateTable()); */ } generateTable(): void { diff --git a/src/components/chart.vue b/src/components/chart.vue index d0dd299..fc23fb5 100644 --- a/src/components/chart.vue +++ b/src/components/chart.vue @@ -26,6 +26,8 @@ import { import { DVChart } from './../classes/chart'; import { charts } from './../store/main'; +import { isArray } from './../utils'; + import ChartSlider from './../components/chart-slider.vue'; const log: loglevel.Logger = loglevel.getLogger('dv-chart'); @@ -33,35 +35,12 @@ const log: loglevel.Logger = loglevel.getLogger('dv-chart'); const DV_CHART_CONTAINER_ELEMENT = '[dv-chart-container]'; const DV_CHART_TABLE_CONTAINER_ELEMENT = '[dv-chart-table-container]'; -export type RenderedEvent = { chartId: string; highchartObject: DVHighcharts.ChartObject }; -export type ViewDataEvent = { chartId: string }; -export type SetExtremesEvent = { - chartId: string; - axis: 'xAxis' | 'yAxis'; - max: number; - min: number; -}; - -// `menuItemDefinitions` is not included in default Highcharts exporting options types -interface EnhancedExportingOptions extends Highcharts.ExportingOptions { - menuItemDefinitions: { [name: string]: DVHighcharts.MenuItem | null }; -} - @Component({ components: { 'dv-chart-slider': ChartSlider } }) export default class Chart extends Vue { - /* private static _rendered: Subject = new Subject(); - static rendered = Chart._rendered.asObservable(); - - private static _viewData: Subject = new Subject(); - static viewData = Chart._viewData.asObservable(); - - private static _setExtremes: Subject = new Subject(); - static setExtremes = Chart._setExtremes.asObservable(); */ - dvchart: DVChart; isLoading: boolean = true; @@ -155,63 +134,82 @@ export default class Chart extends Vue { renderChart(): void { log.info(`[chart='${this.id}'] rendering chart`); - // merge the custom `viewData` export option into the chart config - // if no exporting options exist, they will be created - // if the config author has specified `viewData` export option, it will not be overwritten - this.dvchart.config!.exporting = deepmerge.all([ - { + // create a deep copy of the original config for reference + const originalConfig: DVHighcharts.Options = deepmerge({}, this.dvchart.config); + + const originalViewDataOption: DVHighcharts.MenuItem | undefined = originalConfig.exporting + .menuItemDefinitions.viewData!; + + // create an update snippet for the config based on the DQV options (table generation is the only one for now) + const configUpdates: DVHighcharts.Options = { + exporting: { menuItemDefinitions: { - viewData: this._viewDataExportOption + viewData: + // if no exporting options exist (`undefined` only, `null` has meaning in chart config - hide option), an internal one will be supplied + // if the config author has specified `viewData` export option, it will not be overwritten + typeof originalViewDataOption === 'undefined' + ? this._viewDataExportOption + : originalViewDataOption } - } as EnhancedExportingOptions, - (this.dvchart.config!.exporting || {}) as Object - ]) as Highcharts.ExportingOptions; - - // TODO: handle yAxis - // TODO: if a 'setExtreme' event handler is already specified on the in the chart config, wrap it and execute it after the main handler - this.dvchart.config!.xAxis = deepmerge.all([ - { + }, + // TODO: handle yAxis + // TODO: handle multipl x axes + // it's possible to specify several x axes for a chart + // in this case, each axis's options needs to be overwritten separately + + /* const axes = isArray(originalConfig.xAxis) + ? originalConfig.xAxis + : [originalConfig.xAxis]; + + axes.forEach((axis: Highcharts.AxisOptions) => + this._setExtremesHandler(event, axis.events!.setExtremes!) + ); */ + xAxis: { events: { - setExtremes: (event: { max: number; min: number }) => { - chartSetExtremes.next({ - chartId: this.id, - dvchart: this.dvchart, - axis: 'xAxis', - max: event.max, - min: event.min - }); - /* Chart._setExtremes.next({ - chartId: this.id, - axis: 'xAxis', - max: event.max, - min: event.min - }); */ + setExtremes: (event: Highcharts.AxisEvent) => { + this._setExtremesHandler( + event, + (originalConfig.xAxis!).events!.setExtremes! + ); } } - } as Object, - (this.dvchart.config!.xAxis || {}) as Object - ]) as Object; + } + }; + + // update the original config + const modifiedConfig: DVHighcharts.Options = deepmerge(this.dvchart.config, configUpdates); // create an actual Highcharts object - this.highchartObject = api.Highcharts.chart(this._chartContainer, this.dvchart - .config as Highcharts.Options); + this.highchartObject = api.Highcharts.chart(this._chartContainer, modifiedConfig); this.isLoading = false; + // push an event into the chart rendered stream chartRendered.next({ chartId: this.id, dvchart: this.dvchart, highchartObject: this.highchartObject as DVHighcharts.ChartObject }); - /* Chart._rendered.next({ - chartId: this.id, - highchartObject: this.highchartObject as DVHighcharts.ChartObject - }); */ - if (this.autoGenerateTable) { this.simulateViewDataClick(); } } + + // execute the original `setExtremes` handler specified in the chart config and push a `setExtremes` event into the corresponding stream + private _setExtremesHandler( + event: DVHighcharts.AxisEvent, + originalHandler: (event: DVHighcharts.AxisEvent) => void + ): void { + originalHandler.call(event.target, event); + + chartSetExtremes.next({ + chartId: this.id, + dvchart: this.dvchart, + axis: 'xAxis', + max: event.max, + min: event.min + }); + } } diff --git a/src/index.ejs b/src/index.ejs index 476ca3a..2723d19 100644 --- a/src/index.ejs +++ b/src/index.ejs @@ -151,7 +151,7 @@ ' ' + ' ' + - ' [{{ count }}]' + + ' [{{ count }}] ' + ' ' ; @@ -291,7 +291,13 @@ return this.value; // clean, unformatted number for year } }, - type: 'datetime' + type: 'datetime', + events: { + setExtremes: function (event) { + console.log('nuclear `setExtremes` external handle'); + document.getElementById('nuclear-extreme').innerHTML = Math.random().toFixed(2); + } + } }, yAxis: { title: { diff --git a/src/utils.ts b/src/utils.ts index 427579b..8b43d5f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -16,6 +16,10 @@ function isPromise(x: any): x is Promise { ); } +function isArray(x: any): x is T[] { + return Array.isArray(x); +} + enum keyCodes { 'BREAK' = 3, 'BACKSPACE_/_DELETE' = 8, @@ -174,4 +178,4 @@ enum keyCodes { 'TOGGLE_TOUCHPAD' = 255 } -export { isFunction, isString, isObject, isPromise, keyCodes }; +export { isFunction, isString, isObject, isPromise, isArray, keyCodes }; From 9dda712da761b8f62bbe21ffc7000c2f476a246d Mon Sep 17 00:00:00 2001 From: Aleksuei Riabtsev Date: Wed, 17 Jan 2018 14:10:29 -0500 Subject: [PATCH 3/3] update docs build --- docs/samples/dqvue.js | 3789 +++++++++++++++++++++-------------------- 1 file changed, 1979 insertions(+), 1810 deletions(-) diff --git a/docs/samples/dqvue.js b/docs/samples/dqvue.js index 11b70a1..a1bbb8f 100644 --- a/docs/samples/dqvue.js +++ b/docs/samples/dqvue.js @@ -61,11 +61,38 @@ var DQV = /******/ __webpack_require__.p = "/"; /******/ /******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 29); +/******/ return __webpack_require__(__webpack_require__.s = 31); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ +/*!*******************************!*\ + !*** ./src/observable-bus.ts ***! + \*******************************/ +/*! exports provided: chartCreated, chartRendered, chartConfigUpdated, chartViewData, chartSetExtremes, sectionCreated */ +/*! exports used: chartConfigUpdated, chartCreated, chartRendered, chartSetExtremes, chartViewData, sectionCreated */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return chartCreated; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return chartRendered; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return chartConfigUpdated; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "e", function() { return chartViewData; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "d", function() { return chartSetExtremes; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "f", function() { return sectionCreated; }); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_rxjs_Subject__ = __webpack_require__(/*! rxjs/Subject */ 54); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_rxjs_Subject___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_rxjs_Subject__); + +var chartCreated = new __WEBPACK_IMPORTED_MODULE_0_rxjs_Subject__["Subject"](); +var chartRendered = new __WEBPACK_IMPORTED_MODULE_0_rxjs_Subject__["Subject"](); +var chartConfigUpdated = new __WEBPACK_IMPORTED_MODULE_0_rxjs_Subject__["Subject"](); +var chartViewData = new __WEBPACK_IMPORTED_MODULE_0_rxjs_Subject__["Subject"](); +var chartSetExtremes = new __WEBPACK_IMPORTED_MODULE_0_rxjs_Subject__["Subject"](); +var sectionCreated = new __WEBPACK_IMPORTED_MODULE_0_rxjs_Subject__["Subject"](); + + +/***/ }), +/* 1 */ /*!***********************************************!*\ !*** ./node_modules/loglevel/lib/loglevel.js ***! \***********************************************/ @@ -324,7 +351,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;/* /***/ }), -/* 1 */ +/* 2 */ /*!*****************************************!*\ !*** ./node_modules/rxjs/Observable.js ***! \*****************************************/ @@ -335,10 +362,10 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;/* "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var root_1 = __webpack_require__(/*! ./util/root */ 10); -var toSubscriber_1 = __webpack_require__(/*! ./util/toSubscriber */ 44); -var observable_1 = __webpack_require__(/*! ./symbol/observable */ 48); -var pipe_1 = __webpack_require__(/*! ./util/pipe */ 49); +var root_1 = __webpack_require__(/*! ./util/root */ 11); +var toSubscriber_1 = __webpack_require__(/*! ./util/toSubscriber */ 45); +var observable_1 = __webpack_require__(/*! ./symbol/observable */ 49); +var pipe_1 = __webpack_require__(/*! ./util/pipe */ 50); /** * A representation of any set of values over any amount of time. This is the most basic building block * of RxJS. @@ -641,7 +668,7 @@ exports.Observable = Observable; //# sourceMappingURL=Observable.js.map /***/ }), -/* 2 */ +/* 3 */ /*!*******************************************!*\ !*** ./node_modules/rxjs/Subscription.js ***! \*******************************************/ @@ -652,12 +679,12 @@ exports.Observable = Observable; "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var isArray_1 = __webpack_require__(/*! ./util/isArray */ 45); -var isObject_1 = __webpack_require__(/*! ./util/isObject */ 46); -var isFunction_1 = __webpack_require__(/*! ./util/isFunction */ 17); +var isArray_1 = __webpack_require__(/*! ./util/isArray */ 46); +var isObject_1 = __webpack_require__(/*! ./util/isObject */ 47); +var isFunction_1 = __webpack_require__(/*! ./util/isFunction */ 16); var tryCatch_1 = __webpack_require__(/*! ./util/tryCatch */ 25); -var errorObject_1 = __webpack_require__(/*! ./util/errorObject */ 18); -var UnsubscriptionError_1 = __webpack_require__(/*! ./util/UnsubscriptionError */ 47); +var errorObject_1 = __webpack_require__(/*! ./util/errorObject */ 17); +var UnsubscriptionError_1 = __webpack_require__(/*! ./util/UnsubscriptionError */ 48); /** * Represents a disposable resource, such as the execution of an Observable. A * Subscription has one important method, `unsubscribe`, that takes no argument @@ -846,7 +873,7 @@ function flattenUnsubscriptionErrors(errors) { //# sourceMappingURL=Subscription.js.map /***/ }), -/* 3 */ +/* 4 */ /*!***************************!*\ !*** ./src/store/main.ts ***! \***************************/ @@ -857,29 +884,29 @@ function flattenUnsubscriptionErrors(errors) { "use strict"; /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return sections; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return charts; }); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__observable_bus__ = __webpack_require__(/*! ./../observable-bus */ 13); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__observable_bus__ = __webpack_require__(/*! ./../observable-bus */ 0); var sections = {}; var charts = {}; -function addSection(section) { - if (sections[section.id]) { +function addSection(event) { + if (sections[event.sectionId]) { return; } - sections[section.id] = section; + sections[event.sectionId] = event.dvsection; } -function addChart(chart) { - if (charts[chart.id]) { +function addChart(event) { + if (charts[event.chartId]) { return; } - charts[chart.id] = chart; + charts[event.chartId] = event.dvchart; } -__WEBPACK_IMPORTED_MODULE_0__observable_bus__["b" /* sectionCreatedSubject */].subscribe(addSection); -__WEBPACK_IMPORTED_MODULE_0__observable_bus__["a" /* chartCreatedSubject */].subscribe(addChart); +__WEBPACK_IMPORTED_MODULE_0__observable_bus__["f" /* sectionCreated */].subscribe(addSection); +__WEBPACK_IMPORTED_MODULE_0__observable_bus__["b" /* chartCreated */].subscribe(addChart); /***/ }), -/* 4 */ +/* 5 */ /*!***********************************!*\ !*** (webpack)/buildin/global.js ***! \***********************************/ @@ -911,7 +938,7 @@ module.exports = g; /***/ }), -/* 5 */ +/* 6 */ /*!*************************************************!*\ !*** ./node_modules/css-loader/lib/css-base.js ***! \*************************************************/ @@ -998,7 +1025,7 @@ function toComment(sourceMap) { /***/ }), -/* 6 */ +/* 7 */ /*!**************************************************************!*\ !*** ./node_modules/vue-style-loader/lib/addStylesClient.js ***! \**************************************************************/ @@ -1022,7 +1049,7 @@ if (typeof DEBUG !== 'undefined' && DEBUG) { ) } } -var listToStyles = __webpack_require__(/*! ./listToStyles */ 36) +var listToStyles = __webpack_require__(/*! ./listToStyles */ 38) /* type StyleObject = { @@ -1224,7 +1251,7 @@ function applyToTag (styleElement, obj) { /***/ }), -/* 7 */ +/* 8 */ /*!*************************************************************!*\ !*** ./node_modules/vue-loader/lib/component-normalizer.js ***! \*************************************************************/ @@ -1338,7 +1365,7 @@ module.exports = function normalizeComponent ( /***/ }), -/* 8 */ +/* 9 */ /*!*******************************************************************************!*\ !*** ./node_modules/vue-property-decorator/lib/vue-property-decorator.umd.js ***! \*******************************************************************************/ @@ -1347,7 +1374,7 @@ module.exports = function normalizeComponent ( /***/ (function(module, exports, __webpack_require__) { (function (global, factory) { - true ? factory(exports, __webpack_require__(/*! vue */ 14), __webpack_require__(/*! vue-class-component */ 38), __webpack_require__(/*! reflect-metadata */ 39)) : + true ? factory(exports, __webpack_require__(/*! vue */ 13), __webpack_require__(/*! vue-class-component */ 39), __webpack_require__(/*! reflect-metadata */ 40)) : typeof define === 'function' && define.amd ? define(['exports', 'vue', 'vue-class-component', 'reflect-metadata'], factory) : (factory((global.VuePropertyDecorator = {}),global.Vue,global.VueClassComponent)); }(this, (function (exports,vue,vueClassComponent) { 'use strict'; @@ -1480,47 +1507,23 @@ Object.defineProperty(exports, '__esModule', { value: true }); /***/ }), -/* 9 */ -/*!**********************************!*\ - !*** ./src/components/chart.vue ***! - \**********************************/ -/*! exports provided: default */ -/*! exports used: default */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 10 */ +/*!**************************************************!*\ + !*** ./node_modules/rxjs/add/operator/filter.js ***! + \**************************************************/ +/*! dynamic exports provided */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__ts_loader_node_modules_vue_loader_lib_selector_type_script_index_0_chart_vue__ = __webpack_require__(/*! !ts-loader!../../node_modules/vue-loader/lib/selector?type=script&index=0!./chart.vue */ 43); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__node_modules_vue_loader_lib_template_compiler_index_id_data_v_2a992506_hasScoped_true_buble_transforms_node_modules_vue_loader_lib_selector_type_template_index_0_chart_vue__ = __webpack_require__(/*! !../../node_modules/vue-loader/lib/template-compiler/index?{"id":"data-v-2a992506","hasScoped":true,"buble":{"transforms":{}}}!../../node_modules/vue-loader/lib/selector?type=template&index=0!./chart.vue */ 71); -function injectStyle (ssrContext) { - __webpack_require__(/*! !vue-style-loader?{"sourceMap":true}!css-loader?{"autoprefixer":false,"sourceMap":true,"minimize":false}!../../node_modules/vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-2a992506","scoped":true,"hasInlineConfig":true}!sass-loader?{"sourceMap":true}!../../node_modules/vue-loader/lib/selector?type=styles&index=0!./chart.vue */ 41) -} -var normalizeComponent = __webpack_require__(/*! ../../node_modules/vue-loader/lib/component-normalizer */ 7) -/* script */ - -/* template */ - -/* template functional */ -var __vue_template_functional__ = false -/* styles */ -var __vue_styles__ = injectStyle -/* scopeId */ -var __vue_scopeId__ = "data-v-2a992506" -/* moduleIdentifier (server only) */ -var __vue_module_identifier__ = null -var Component = normalizeComponent( - __WEBPACK_IMPORTED_MODULE_0__ts_loader_node_modules_vue_loader_lib_selector_type_script_index_0_chart_vue__["a" /* default */], - __WEBPACK_IMPORTED_MODULE_1__node_modules_vue_loader_lib_template_compiler_index_id_data_v_2a992506_hasScoped_true_buble_transforms_node_modules_vue_loader_lib_selector_type_template_index_0_chart_vue__["a" /* default */], - __vue_template_functional__, - __vue_styles__, - __vue_scopeId__, - __vue_module_identifier__ -) - -/* harmony default export */ __webpack_exports__["a"] = (Component.exports); +Object.defineProperty(exports, "__esModule", { value: true }); +var Observable_1 = __webpack_require__(/*! ../../Observable */ 2); +var filter_1 = __webpack_require__(/*! ../../operator/filter */ 52); +Observable_1.Observable.prototype.filter = filter_1.filter; +//# sourceMappingURL=filter.js.map /***/ }), -/* 10 */ +/* 11 */ /*!****************************************!*\ !*** ./node_modules/rxjs/util/root.js ***! \****************************************/ @@ -1549,10 +1552,10 @@ exports.root = _root; } })(); //# sourceMappingURL=root.js.map -/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(/*! ./../../webpack/buildin/global.js */ 4))) +/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(/*! ./../../webpack/buildin/global.js */ 5))) /***/ }), -/* 11 */ +/* 12 */ /*!*****************************************!*\ !*** ./node_modules/rxjs/Subscriber.js ***! \*****************************************/ @@ -1573,10 +1576,10 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var isFunction_1 = __webpack_require__(/*! ./util/isFunction */ 17); -var Subscription_1 = __webpack_require__(/*! ./Subscription */ 2); +var isFunction_1 = __webpack_require__(/*! ./util/isFunction */ 16); +var Subscription_1 = __webpack_require__(/*! ./Subscription */ 3); var Observer_1 = __webpack_require__(/*! ./Observer */ 26); -var rxSubscriber_1 = __webpack_require__(/*! ./symbol/rxSubscriber */ 19); +var rxSubscriber_1 = __webpack_require__(/*! ./symbol/rxSubscriber */ 18); /** * Implements the {@link Observer} interface and extends the * {@link Subscription} class. While the {@link Observer} is the public API for @@ -1834,44 +1837,8 @@ var SafeSubscriber = /** @class */ (function (_super) { }(Subscriber)); //# sourceMappingURL=Subscriber.js.map -/***/ }), -/* 12 */ -/*!**************************************************!*\ - !*** ./node_modules/rxjs/add/operator/filter.js ***! - \**************************************************/ -/*! dynamic exports provided */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -var Observable_1 = __webpack_require__(/*! ../../Observable */ 1); -var filter_1 = __webpack_require__(/*! ../../operator/filter */ 53); -Observable_1.Observable.prototype.filter = filter_1.filter; -//# sourceMappingURL=filter.js.map - /***/ }), /* 13 */ -/*!*******************************!*\ - !*** ./src/observable-bus.ts ***! - \*******************************/ -/*! exports provided: sectionCreatedSubject, chartCreatedSubject */ -/*! exports used: chartCreatedSubject, sectionCreatedSubject */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return sectionCreatedSubject; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return chartCreatedSubject; }); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_rxjs_Subject__ = __webpack_require__(/*! rxjs/Subject */ 16); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_rxjs_Subject___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_rxjs_Subject__); - -var sectionCreatedSubject = new __WEBPACK_IMPORTED_MODULE_0_rxjs_Subject__["Subject"](); -var chartCreatedSubject = new __WEBPACK_IMPORTED_MODULE_0_rxjs_Subject__["Subject"](); - - - -/***/ }), -/* 14 */ /*!******************************************!*\ !*** ./node_modules/vue/dist/vue.esm.js ***! \******************************************/ @@ -1882,7 +1849,7 @@ var chartCreatedSubject = new __WEBPACK_IMPORTED_MODULE_0_rxjs_Subject__["Subjec "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* WEBPACK VAR INJECTION */(function(global, setImmediate) {/*! - * Vue.js v2.5.4 + * Vue.js v2.5.13 * (c) 2014-2017 Evan You * Released under the MIT License. */ @@ -1915,6 +1882,8 @@ function isPrimitive (value) { return ( typeof value === 'string' || typeof value === 'number' || + // $flow-disable-line + typeof value === 'symbol' || typeof value === 'boolean' ) } @@ -2223,6 +2192,7 @@ var config = ({ /** * Option merge strategies (used in core/util/options) */ + // $flow-disable-line optionMergeStrategies: Object.create(null), /** @@ -2263,6 +2233,7 @@ var config = ({ /** * Custom user key aliases for v-on */ + // $flow-disable-line keyCodes: Object.create(null), /** @@ -2598,9 +2569,9 @@ var VNode = function VNode ( this.elm = elm; this.ns = undefined; this.context = context; - this.functionalContext = undefined; - this.functionalOptions = undefined; - this.functionalScopeId = undefined; + this.fnContext = undefined; + this.fnOptions = undefined; + this.fnScopeId = undefined; this.key = data && data.key; this.componentOptions = componentOptions; this.componentInstance = undefined; @@ -2659,6 +2630,9 @@ function cloneVNode (vnode, deep) { cloned.isStatic = vnode.isStatic; cloned.key = vnode.key; cloned.isComment = vnode.isComment; + cloned.fnContext = vnode.fnContext; + cloned.fnOptions = vnode.fnOptions; + cloned.fnScopeId = vnode.fnScopeId; cloned.isCloned = true; if (deep) { if (vnode.children) { @@ -2694,8 +2668,7 @@ var arrayMethods = Object.create(arrayProto);[ 'splice', 'sort', 'reverse' -] -.forEach(function (method) { +].forEach(function (method) { // cache original method var original = arrayProto[method]; def(arrayMethods, method, function mutator () { @@ -3027,18 +3000,18 @@ function mergeDataOrFn ( // it has to be a function to pass previous merges. return function mergedDataFn () { return mergeData( - typeof childVal === 'function' ? childVal.call(this) : childVal, - typeof parentVal === 'function' ? parentVal.call(this) : parentVal + typeof childVal === 'function' ? childVal.call(this, this) : childVal, + typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal ) } } else { return function mergedInstanceDataFn () { // instance merge var instanceData = typeof childVal === 'function' - ? childVal.call(vm) + ? childVal.call(vm, vm) : childVal; var defaultData = typeof parentVal === 'function' - ? parentVal.call(vm) + ? parentVal.call(vm, vm) : parentVal; if (instanceData) { return mergeData(instanceData, defaultData) @@ -3190,13 +3163,23 @@ var defaultStrat = function (parentVal, childVal) { */ function checkComponents (options) { for (var key in options.components) { - var lower = key.toLowerCase(); - if (isBuiltInTag(lower) || config.isReservedTag(lower)) { - warn( - 'Do not use built-in or reserved HTML elements as component ' + - 'id: ' + key - ); - } + validateComponentName(key); + } +} + +function validateComponentName (name) { + if (!/^[a-zA-Z][\w-]*$/.test(name)) { + warn( + 'Invalid component name: "' + name + '". Component names ' + + 'can only contain alphanumeric characters and the hyphen, ' + + 'and must start with a letter.' + ); + } + if (isBuiltInTag(name) || config.isReservedTag(name)) { + warn( + 'Do not use built-in or reserved HTML elements as component ' + + 'id: ' + name + ); } } @@ -3243,6 +3226,7 @@ function normalizeProps (options, vm) { */ function normalizeInject (options, vm) { var inject = options.inject; + if (!inject) { return } var normalized = options.inject = {}; if (Array.isArray(inject)) { for (var i = 0; i < inject.length; i++) { @@ -3397,7 +3381,9 @@ function validateProp ( observe(value); observerState.shouldConvert = prevShouldConvert; } - if (false) { + if ( + false + ) { assertProp(prop, key, value, vm, absent); } return value @@ -3814,7 +3800,7 @@ function traverse (val) { function _traverse (val, seen) { var i, keys; var isA = Array.isArray(val); - if ((!isA && !isObject(val)) || !Object.isExtensible(val)) { + if ((!isA && !isObject(val)) || Object.isFrozen(val)) { return } if (val.__ob__) { @@ -3877,11 +3863,12 @@ function updateListeners ( remove$$1, vm ) { - var name, cur, old, event; + var name, def, cur, old, event; for (name in on) { - cur = on[name]; + def = cur = on[name]; old = oldOn[name]; event = normalizeEvent(name); + /* istanbul ignore if */ if (isUndef(cur)) { "production" !== 'production' && warn( "Invalid handler for event \"" + (event.name) + "\": got " + String(cur), @@ -3891,7 +3878,7 @@ function updateListeners ( if (isUndef(cur.fns)) { cur = on[name] = createFnInvoker(cur); } - add(event.name, cur, event.once, event.capture, event.passive); + add(event.name, cur, event.once, event.capture, event.passive, event.params); } else if (cur !== old) { old.fns = cur; on[name] = old; @@ -4385,6 +4372,8 @@ function eventsMixin (Vue) { /* */ + + /** * Runtime helper for resolving raw children VNodes into a slot object. */ @@ -4405,13 +4394,13 @@ function resolveSlots ( } // named slots should only be respected if the vnode was rendered in the // same context. - if ((child.context === context || child.functionalContext === context) && + if ((child.context === context || child.fnContext === context) && data && data.slot != null ) { - var name = child.data.slot; + var name = data.slot; var slot = (slots[name] || (slots[name] = [])); if (child.tag === 'template') { - slot.push.apply(slot, child.children); + slot.push.apply(slot, child.children || []); } else { slot.push(child); } @@ -4625,7 +4614,10 @@ function mountComponent ( }; } - vm._watcher = new Watcher(vm, updateComponent, noop); + // we set this to vm._watcher inside the watcher's constructor + // since the watcher's initial patch may call $forceUpdate (e.g. inside child + // component's mounted hook), which relies on vm._watcher being already defined + new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */); hydrating = false; // manually mounted instance, call mounted on self @@ -4912,9 +4904,13 @@ var Watcher = function Watcher ( vm, expOrFn, cb, - options + options, + isRenderWatcher ) { this.vm = vm; + if (isRenderWatcher) { + vm._watcher = this; + } vm._watchers.push(this); // options if (options) { @@ -5245,6 +5241,7 @@ function getData (data, vm) { var computedWatcherOptions = { lazy: true }; function initComputed (vm, computed) { + // $flow-disable-line var watchers = vm._computedWatchers = Object.create(null); // computed properties are just getters during SSR var isSSR = isServerRendering(); @@ -5474,11 +5471,11 @@ function resolveInject (inject, vm) { // inject is :any because flow is not smart enough to figure out cached var result = Object.create(null); var keys = hasSymbol - ? Reflect.ownKeys(inject).filter(function (key) { - /* istanbul ignore next */ - return Object.getOwnPropertyDescriptor(inject, key).enumerable - }) - : Object.keys(inject); + ? Reflect.ownKeys(inject).filter(function (key) { + /* istanbul ignore next */ + return Object.getOwnPropertyDescriptor(inject, key).enumerable + }) + : Object.keys(inject); for (var i = 0; i < keys.length; i++) { var key = keys[i]; @@ -5684,19 +5681,9 @@ function bindObjectProps ( */ function renderStatic ( index, - isInFor, - isOnce + isInFor ) { - // render fns generated by compiler < 2.5.4 does not provide v-once - // information to runtime so be conservative - var isOldVersion = arguments.length < 3; - // if a static tree is generated by v-once, it is cached on the instance; - // otherwise it is purely static and can be cached on the shared options - // across all instances. - var renderFns = this.$options.staticRenderFns; - var cached = isOldVersion || isOnce - ? (this._staticTrees || (this._staticTrees = [])) - : (renderFns.cached || (renderFns.cached = [])); + var cached = this._staticTrees || (this._staticTrees = []); var tree = cached[index]; // if has already-rendered static tree and not inside v-for, // we can reuse the same tree by doing a shallow clone. @@ -5706,7 +5693,11 @@ function renderStatic ( : cloneVNode(tree) } // otherwise, render a fresh tree. - tree = cached[index] = renderFns[index].call(this._renderProxy, null, this); + tree = cached[index] = this.$options.staticRenderFns[index].call( + this._renderProxy, + null, + this // for render fns generated for functional component templates + ); markStatic(tree, ("__static__" + index), false); return tree } @@ -5824,8 +5815,8 @@ function FunctionalRenderContext ( this._c = function (a, b, c, d) { var vnode = createElement(contextVm, a, b, c, d, needNormalization); if (vnode) { - vnode.functionalScopeId = options._scopeId; - vnode.functionalContext = parent; + vnode.fnScopeId = options._scopeId; + vnode.fnContext = parent; } return vnode }; @@ -5866,8 +5857,8 @@ function createFunctionalComponent ( var vnode = options.render.call(null, renderContext._c, renderContext); if (vnode instanceof VNode) { - vnode.functionalContext = contextVm; - vnode.functionalOptions = options; + vnode.fnContext = contextVm; + vnode.fnOptions = options; if (data.slot) { (vnode.data || (vnode.data = {})).slot = data.slot; } @@ -5884,6 +5875,25 @@ function mergeProps (to, from) { /* */ + + + +// Register the component hook to weex native render engine. +// The hook will be triggered by native, not javascript. + + +// Updates the state of the component to weex native render engine. + +/* */ + +// https://github.com/Hanks10100/weex-native-directive/tree/master/component + +// listening on native callback + +/* */ + +/* */ + // hooks to be invoked on component VNodes during patch var componentVNodeHooks = { init: function init ( @@ -6049,6 +6059,11 @@ function createComponent ( { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children }, asyncFactory ); + + // Weex specific: invoke recycle-list optimized @render function for + // extracting cell-slot template. + // https://github.com/Hanks10100/weex-native-directive/tree/master/component + /* istanbul ignore if */ return vnode } @@ -6058,15 +6073,10 @@ function createComponentInstanceForVnode ( parentElm, refElm ) { - var vnodeComponentOptions = vnode.componentOptions; var options = { _isComponent: true, parent: parent, - propsData: vnodeComponentOptions.propsData, - _componentTag: vnodeComponentOptions.tag, _parentVnode: vnode, - _parentListeners: vnodeComponentOptions.listeners, - _renderChildren: vnodeComponentOptions.children, _parentElm: parentElm || null, _refElm: refElm || null }; @@ -6076,7 +6086,7 @@ function createComponentInstanceForVnode ( options.render = inlineTemplate.render; options.staticRenderFns = inlineTemplate.staticRenderFns; } - return new vnodeComponentOptions.Ctor(options) + return new vnode.componentOptions.Ctor(options) } function mergeHooks (data) { @@ -6163,11 +6173,13 @@ function _createElement ( // warn against non-primitive key if (false ) { - warn( - 'Avoid using non-primitive value as key, ' + - 'use string/number value instead.', - context - ); + { + warn( + 'Avoid using non-primitive value as key, ' + + 'use string/number value instead.', + context + ); + } } // support single function children as default scoped slot if (Array.isArray(children) && @@ -6237,6 +6249,7 @@ function applyNS (vnode, ns, force) { function initRender (vm) { vm._vnode = null; // the root of the child tree + vm._staticTrees = null; // v-once cached trees var options = vm.$options; var parentVnode = vm.$vnode = options._parentVnode; // the placeholder node in parent tree var renderContext = parentVnode && parentVnode.context; @@ -6408,14 +6421,18 @@ function initMixin (Vue) { function initInternalComponent (vm, options) { var opts = vm.$options = Object.create(vm.constructor.options); // doing this because it's faster than dynamic enumeration. + var parentVnode = options._parentVnode; opts.parent = options.parent; - opts.propsData = options.propsData; - opts._parentVnode = options._parentVnode; - opts._parentListeners = options._parentListeners; - opts._renderChildren = options._renderChildren; - opts._componentTag = options._componentTag; + opts._parentVnode = parentVnode; opts._parentElm = options._parentElm; opts._refElm = options._refElm; + + var vnodeComponentOptions = parentVnode.componentOptions; + opts.propsData = vnodeComponentOptions.propsData; + opts._parentListeners = vnodeComponentOptions.listeners; + opts._renderChildren = vnodeComponentOptions.children; + opts._componentTag = vnodeComponentOptions.tag; + if (options.render) { opts.render = options.render; opts.staticRenderFns = options.staticRenderFns; @@ -6549,13 +6566,7 @@ function initExtend (Vue) { var name = extendOptions.name || Super.options.name; if (false) { - if (!/^[a-zA-Z][\w-]*$/.test(name)) { - warn( - 'Invalid component name: "' + name + '". Component names ' + - 'can only contain alphanumeric characters and the hyphen, ' + - 'and must start with a letter.' - ); - } + validateComponentName(name); } var Sub = function VueComponent (options) { @@ -6638,12 +6649,7 @@ function initAssetRegisters (Vue) { } else { /* istanbul ignore if */ if (false) { - if (type === 'component' && config.isReservedTag(id)) { - warn( - 'Do not use built-in or reserved HTML elements as component ' + - 'id: ' + id - ); - } + validateComponentName(id); } if (type === 'component' && isPlainObject(definition)) { definition.name = definition.name || id; @@ -6699,7 +6705,7 @@ function pruneCacheEntry ( current ) { var cached$$1 = cache[key]; - if (cached$$1 && cached$$1 !== current) { + if (cached$$1 && (!current || cached$$1.tag !== current.tag)) { cached$$1.componentInstance.$destroy(); } cache[key] = null; @@ -6741,21 +6747,27 @@ var KeepAlive = { }, render: function render () { - var vnode = getFirstComponentChild(this.$slots.default); + var slot = this.$slots.default; + var vnode = getFirstComponentChild(slot); var componentOptions = vnode && vnode.componentOptions; if (componentOptions) { // check pattern var name = getComponentName(componentOptions); - if (name && ( - (this.exclude && matches(this.exclude, name)) || - (this.include && !matches(this.include, name)) - )) { + var ref = this; + var include = ref.include; + var exclude = ref.exclude; + if ( + // not included + (include && (!name || !matches(include, name))) || + // excluded + (exclude && name && matches(exclude, name)) + ) { return vnode } - var ref = this; - var cache = ref.cache; - var keys = ref.keys; + var ref$1 = this; + var cache = ref$1.cache; + var keys = ref$1.keys; var key = vnode.key == null // same constructor may get registered as different local components // so cid alone is not enough (#3269) @@ -6777,7 +6789,7 @@ var KeepAlive = { vnode.data.keepAlive = true; } - return vnode + return vnode || (slot && slot[0]) } }; @@ -6844,7 +6856,7 @@ Object.defineProperty(Vue$3.prototype, '$ssrContext', { } }); -Vue$3.version = '2.5.4'; +Vue$3.version = '2.5.13'; /* */ @@ -6896,12 +6908,12 @@ function genClassForVnode (vnode) { var childNode = vnode; while (isDef(childNode.componentInstance)) { childNode = childNode.componentInstance._vnode; - if (childNode.data) { + if (childNode && childNode.data) { data = mergeClassData(childNode.data, data); } } while (isDef(parentNode = parentNode.parent)) { - if (parentNode.data) { + if (parentNode && parentNode.data) { data = mergeClassData(data, parentNode.data); } } @@ -7412,11 +7424,14 @@ function createPatchFunction (backend) { function createChildren (vnode, children, insertedVnodeQueue) { if (Array.isArray(children)) { + if (false) { + checkDuplicateKeys(children); + } for (var i = 0; i < children.length; ++i) { createElm(children[i], insertedVnodeQueue, vnode.elm, null, true); } } else if (isPrimitive(vnode.text)) { - nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(vnode.text)); + nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text))); } } @@ -7443,7 +7458,7 @@ function createPatchFunction (backend) { // of going through the normal attribute patching process. function setScope (vnode) { var i; - if (isDef(i = vnode.functionalScopeId)) { + if (isDef(i = vnode.fnScopeId)) { nodeOps.setAttribute(vnode.elm, i, ''); } else { var ancestor = vnode; @@ -7457,7 +7472,7 @@ function createPatchFunction (backend) { // for slot content they should also get the scopeId from the host instance. if (isDef(i = activeInstance) && i !== vnode.context && - i !== vnode.functionalContext && + i !== vnode.fnContext && isDef(i = i.$options._scopeId) ) { nodeOps.setAttribute(vnode.elm, i, ''); @@ -7543,6 +7558,10 @@ function createPatchFunction (backend) { // during leaving transitions var canMove = !removeOnly; + if (false) { + checkDuplicateKeys(newCh); + } + while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { if (isUndef(oldStartVnode)) { oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left @@ -7575,13 +7594,6 @@ function createPatchFunction (backend) { createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm); } else { vnodeToMove = oldCh[idxInOld]; - /* istanbul ignore if */ - if (false) { - warn( - 'It seems there are duplicate keys that is causing an update error. ' + - 'Make sure each v-for item has a unique key.' - ); - } if (sameVnode(vnodeToMove, newStartVnode)) { patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue); oldCh[idxInOld] = undefined; @@ -7602,6 +7614,24 @@ function createPatchFunction (backend) { } } + function checkDuplicateKeys (children) { + var seenKeys = {}; + for (var i = 0; i < children.length; i++) { + var vnode = children[i]; + var key = vnode.key; + if (isDef(key)) { + if (seenKeys[key]) { + warn( + ("Duplicate keys detected: '" + key + "'. This may cause an update error."), + vnode.context + ); + } else { + seenKeys[key] = true; + } + } + } + } + function findIdxInOld (node, oldCh, start, end) { for (var i = start; i < end; i++) { var c = oldCh[i]; @@ -7980,17 +8010,20 @@ function normalizeDirectives$1 ( ) { var res = Object.create(null); if (!dirs) { + // $flow-disable-line return res } var i, dir; for (i = 0; i < dirs.length; i++) { dir = dirs[i]; if (!dir.modifiers) { + // $flow-disable-line dir.modifiers = emptyModifiers; } res[getRawDirName(dir)] = dir; dir.def = resolveAsset(vm.$options, 'directives', dir.name, true); } + // $flow-disable-line return res } @@ -8043,7 +8076,7 @@ function updateAttrs (oldVnode, vnode) { // #4391: in IE9, setting type can reset value for input[type=radio] // #6666: IE/Edge forces progress value down to 1 before setting a max /* istanbul ignore if */ - if ((isIE9 || isEdge) && attrs.value !== oldAttrs.value) { + if ((isIE || isEdge) && attrs.value !== oldAttrs.value) { setAttr(elm, 'value', attrs.value); } for (key in oldAttrs) { @@ -8083,6 +8116,23 @@ function setAttr (el, key, value) { if (isFalsyAttrValue(value)) { el.removeAttribute(key); } else { + // #7138: IE10 & 11 fires input event when setting placeholder on + //