Skip to content

Commit 44b8acd

Browse files
authored
feat(HMR): apply changes in page styles at runtime when app root is a frame (#6857)
* feat(HMR): apply changes in page styles at runtime * fix: livesync tests * test: changeCssFile method * refactor: address comments Add a comment. Update `let` to `const`. Update `changesCssFile` test. * test: add an assert
1 parent 8e9a13c commit 44b8acd

10 files changed

Lines changed: 120 additions & 59 deletions

File tree

‎tests/app/ui/styling/style-tests.ts‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,23 @@ export function test_CSS_isAppliedOnPage_From_addCssFile() {
674674
});
675675
}
676676

677+
export function test_CSS_isAppliedOnPage_From_changeCssFile() {
678+
const testButton = new buttonModule.Button();
679+
testButton.text = "Test";
680+
681+
const testCss = "button { color: blue; }";
682+
683+
const testFunc = function (views: Array<viewModule.View>) {
684+
helper.assertViewColor(testButton, "#0000FF");
685+
const page: pageModule.Page = <pageModule.Page>views[1];
686+
page.changeCssFile("~/ui/styling/test.css");
687+
helper.assertViewBackgroundColor(page, "#FF0000");
688+
TKUnit.assert(testButton.style.color === undefined, "Color should not have a value");
689+
}
690+
691+
helper.buildUIAndRunTest(testButton, testFunc, { pageCss: testCss });
692+
}
693+
677694
const invalidCSS = ".invalid { " +
678695
"color: invalidValue; " +
679696
"background-color: invalidValue; " +

‎tns-core-modules/application/application-common.ts‎

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,23 @@ export function setApplication(instance: iOSApplication | AndroidApplication): v
8282
export function livesync(rootView: View, context?: ModuleContext) {
8383
events.notify(<EventData>{ eventName: "livesync", object: app });
8484
const liveSyncCore = global.__onLiveSyncCore;
85-
let reapplyAppCss = false;
85+
let reapplyAppStyles = false;
86+
let reapplyLocalStyles = false;
8687

87-
if (context) {
88-
const fullFileName = getCssFileName();
89-
const fileName = fullFileName.substring(0, fullFileName.lastIndexOf(".") + 1);
88+
if (context && context.path) {
9089
const extensions = ["css", "scss"];
91-
reapplyAppCss = extensions.some(ext => context.path === fileName.concat(ext));
90+
const appStylesFullFileName = getCssFileName();
91+
const appStylesFileName = appStylesFullFileName.substring(0, appStylesFullFileName.lastIndexOf(".") + 1);
92+
reapplyAppStyles = extensions.some(ext => context.path === appStylesFileName.concat(ext));
93+
if (!reapplyAppStyles) {
94+
reapplyLocalStyles = extensions.some(ext => context.path.endsWith(ext));
95+
}
9296
}
9397

94-
if (reapplyAppCss && rootView) {
98+
if (reapplyAppStyles && rootView) {
9599
rootView._onCssStateChange();
96100
} else if (liveSyncCore) {
97-
liveSyncCore();
101+
reapplyLocalStyles ? liveSyncCore(context) : liveSyncCore();
98102
}
99103
}
100104

‎tns-core-modules/application/application.ios.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,9 @@ class IOSApplication implements IOSApplicationDefinition {
225225
}
226226
}
227227

228-
public _onLivesync(): void {
228+
public _onLivesync(context?: ModuleContext): void {
229229
// If view can't handle livesync set window controller.
230-
if (this._rootView && !this._rootView._onLivesync()) {
230+
if (this._rootView && !this._rootView._onLivesync(context)) {
231231
this.setWindowContent();
232232
}
233233
}
@@ -264,8 +264,8 @@ exports.ios = iosApp;
264264
setApplication(iosApp);
265265

266266
// attach on global, so it can be overwritten in NativeScript Angular
267-
(<any>global).__onLiveSyncCore = function () {
268-
iosApp._onLivesync();
267+
(<any>global).__onLiveSyncCore = function (context?: ModuleContext) {
268+
iosApp._onLivesync(context);
269269
}
270270

271271
let mainEntry: NavigationEntry;

‎tns-core-modules/module.d.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ declare namespace NodeJS {
5252
__inspector?: any;
5353
__extends: any;
5454
__onLiveSync: (context?: { type: string, path: string }) => void;
55-
__onLiveSyncCore: () => void;
55+
__onLiveSyncCore: (context?: { type: string, path: string }) => void;
5656
__onUncaughtError: (error: NativeScriptError) => void;
5757
__onDiscardedError: (error: NativeScriptError) => void;
5858
TNS_WEBPACK?: boolean;

‎tns-core-modules/ui/core/view/view-common.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,14 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
105105
this._updateStyleScope(cssFileName);
106106
}
107107

108+
public changeCssFile(cssFileName: string): void {
109+
const scope = this._styleScope;
110+
if (scope && cssFileName) {
111+
scope.changeCssFile(cssFileName);
112+
this._onCssStateChange();
113+
}
114+
}
115+
108116
public _updateStyleScope(cssFileName?: string, cssString?: string, css?: string): void {
109117
let scope = this._styleScope;
110118
if (!scope) {

‎tns-core-modules/ui/core/view/view.d.ts‎

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ export function PseudoClassHandler(...pseudoClasses: string[]): MethodDecorator;
1818
/**
1919
* Specifies the type name for the instances of this View class,
2020
* that is used when matching CSS type selectors.
21-
*
21+
*
2222
* Usage:
2323
* ```
2424
* @CSSType("Button")
2525
* class Button extends View {
2626
* }
2727
* ```
28-
*
28+
*
2929
* Internally the decorator set `Button.prototype.cssType = "Button"`.
3030
* @param type The type name, e. g. "Button", "Label", etc.
3131
*/
@@ -50,8 +50,8 @@ export type px = number;
5050
export type percent = number;
5151

5252
/**
53-
* The Point interface describes a two dimensional location.
54-
* It has two properties x and y, representing the x and y coordinate of the location.
53+
* The Point interface describes a two dimensional location.
54+
* It has two properties x and y, representing the x and y coordinate of the location.
5555
*/
5656
export interface Point {
5757
/**
@@ -66,8 +66,8 @@ export interface Point {
6666
}
6767

6868
/**
69-
* The Size interface describes abstract dimensions in two dimensional space.
70-
* It has two properties width and height, representing the width and height values of the size.
69+
* The Size interface describes abstract dimensions in two dimensional space.
70+
* It has two properties width and height, representing the width and height values of the size.
7171
*/
7272
export interface Size {
7373
/**
@@ -99,8 +99,8 @@ export interface ShownModallyData extends EventData {
9999
}
100100

101101
/**
102-
* This class is the base class for all UI components.
103-
* A View occupies a rectangular area on the screen and is responsible for drawing and layouting of all UI components within.
102+
* This class is the base class for all UI components.
103+
* A View occupies a rectangular area on the screen and is responsible for drawing and layouting of all UI components within.
104104
*/
105105
export abstract class View extends ViewBase {
106106
/**
@@ -475,13 +475,13 @@ export abstract class View extends ViewBase {
475475
* [Deprecated. Please use the on() instead.] Adds a gesture observer.
476476
* @param type - Type of the gesture.
477477
* @param callback - A function that will be executed when gesture is received.
478-
* @param thisArg - An optional parameter which will be used as `this` context for callback execution.
478+
* @param thisArg - An optional parameter which will be used as `this` context for callback execution.
479479
*/
480480
observe(type: GestureTypes, callback: (args: GestureEventData) => void, thisArg?: any);
481481

482482
/**
483483
* A basic method signature to hook an event listener (shortcut alias to the addEventListener method).
484-
* @param eventNames - String corresponding to events (e.g. "propertyChange"). Optionally could be used more events separated by `,` (e.g. "propertyChange", "change") or you can use gesture types.
484+
* @param eventNames - String corresponding to events (e.g. "propertyChange"). Optionally could be used more events separated by `,` (e.g. "propertyChange", "change") or you can use gesture types.
485485
* @param callback - Callback function which will be executed when event is raised.
486486
* @param thisArg - An optional parameter which will be used as `this` context for callback execution.
487487
*/
@@ -527,12 +527,12 @@ export abstract class View extends ViewBase {
527527
modal: View;
528528

529529
/**
530-
* Animates one or more properties of the view based on the supplied options.
530+
* Animates one or more properties of the view based on the supplied options.
531531
*/
532532
public animate(options: AnimationDefinition): AnimationPromise;
533533

534534
/**
535-
* Creates an Animation object based on the supplied options.
535+
* Creates an Animation object based on the supplied options.
536536
*/
537537
public createAnimation(options: AnimationDefinition): Animation;
538538

@@ -562,7 +562,7 @@ export abstract class View extends ViewBase {
562562
public getActualSize(): Size;
563563

564564
/**
565-
* Derived classes can override this method to handle Android back button press.
565+
* Derived classes can override this method to handle Android back button press.
566566
*/
567567
onBackPressed(): boolean;
568568

@@ -575,7 +575,7 @@ export abstract class View extends ViewBase {
575575
/**
576576
* @private
577577
* Adds a new values to current css.
578-
* @param cssString - A valid css which will be added to current css.
578+
* @param cssString - A valid css which will be added to current css.
579579
*/
580580
addCss(cssString: string): void;
581581

@@ -586,13 +586,20 @@ export abstract class View extends ViewBase {
586586
*/
587587
addCssFile(cssFileName: string): void;
588588

589+
/**
590+
* @private
591+
* Changes the current css to the content of the file.
592+
* @param cssFileName - A valid file name (from the application root) which contains a valid css.
593+
*/
594+
changeCssFile(cssFileName: string): void;
595+
589596
// Lifecycle events
590597
_getNativeViewsCount(): number;
591598

592599
_eachLayoutView(callback: (View) => void): void;
593-
600+
594601
/**
595-
* Iterates over children of type View.
602+
* Iterates over children of type View.
596603
* @param callback Called for each child of type View. Iteration stops if this method returns falsy value.
597604
*/
598605
public eachChildView(callback: (view: View) => boolean): void;
@@ -673,17 +680,17 @@ export abstract class View extends ViewBase {
673680
/**
674681
* @private
675682
*/
676-
_onLivesync(): boolean;
683+
_onLivesync(context?: { type: string, path: string }): boolean;
677684
/**
678685
* @private
679686
*/
680687
_getFragmentManager(): any; /* android.support.v4.app.FragmentManager */
681688

682689
/**
683690
* Updates styleScope or create new styleScope.
684-
* @param cssFileName
685-
* @param cssString
686-
* @param css
691+
* @param cssFileName
692+
* @param cssString
693+
* @param css
687694
*/
688695
_updateStyleScope(cssFileName?: string, cssString?: string, css?: string): void;
689696

@@ -715,7 +722,7 @@ export abstract class View extends ViewBase {
715722
}
716723

717724
/**
718-
* Base class for all UI components that are containers.
725+
* Base class for all UI components that are containers.
719726
*/
720727
export class ContainerView extends View {
721728
/**
@@ -725,7 +732,7 @@ export class ContainerView extends View {
725732
}
726733

727734
/**
728-
* Base class for all UI components that implement custom layouts.
735+
* Base class for all UI components that implement custom layouts.
729736
*/
730737
export class CustomLayoutView extends ContainerView {
731738
//@private

‎tns-core-modules/ui/frame/frame-common.ts‎

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
7676
if (backstackIndex !== -1) {
7777
backstack = backstackIndex;
7878
} else {
79-
// NOTE: We don't search for entries in navigationQueue because there is no way for
79+
// NOTE: We don't search for entries in navigationQueue because there is no way for
8080
// developer to get reference to BackstackEntry unless transition is completed.
8181
// At that point the entry is put in the backstack array.
8282
// If we start to return Backstack entry from navigate method then
@@ -153,7 +153,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
153153
// }
154154

155155
// let currentPage = this._currentEntry.resolvedPage;
156-
// let currentNavigationEntry = this._currentEntry.entry;
156+
// let currentNavigationEntry = this._currentEntry.entry;
157157
// if (currentPage["isBiOrientational"] && currentNavigationEntry.moduleName) {
158158
// if (this.canGoBack()){
159159
// this.goBack();
@@ -162,7 +162,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
162162
// currentNavigationEntry.backstackVisible = false;
163163
// }
164164
// // Re-navigate to the same page so the other (.port or .land) xml is loaded.
165-
// this.navigate(currentNavigationEntry);
165+
// this.navigate(currentNavigationEntry);
166166
// }
167167
// }
168168

@@ -224,7 +224,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
224224
newPage.onNavigatedTo(isBack);
225225

226226
// Reset executing entry after NavigatedTo is raised;
227-
// we do not want to execute two navigations in parallel in case
227+
// we do not want to execute two navigations in parallel in case
228228
// additional navigation is triggered from the NavigatedTo handler.
229229
this._executingEntry = null;
230230
}
@@ -259,7 +259,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
259259
return true;
260260
}
261261
}
262-
262+
263263
return false;
264264
}
265265

@@ -563,14 +563,25 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
563563
return result;
564564
}
565565

566-
public _onLivesync(): boolean {
566+
public _onLivesync(context?: ModuleContext): boolean {
567567
super._onLivesync();
568568

569569
if (!this._currentEntry || !this._currentEntry.entry) {
570570
return false;
571571
}
572572

573573
const currentEntry = this._currentEntry.entry;
574+
if (context && context.path) {
575+
// Use topmost instead of this to cover nested frames scenario
576+
const topmostFrame = topmost();
577+
const moduleName = topmostFrame.currentEntry.moduleName;
578+
const reapplyStyles = context.path.includes(moduleName);
579+
if (reapplyStyles && moduleName) {
580+
topmostFrame.currentPage.changeCssFile(context.path);
581+
return true;
582+
}
583+
}
584+
574585
const newEntry: NavigationEntry = {
575586
animated: false,
576587
clearHistory: true,

0 commit comments

Comments
 (0)