WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit fff8bf8

Browse files
committed
Rewritten several binding handlers into behaviors.
1 parent e45f00b commit fff8bf8

9 files changed

+167
-367
lines changed
Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,33 @@
11
import * as ko from "knockout";
2+
import { AlignBehavior, AlignBehaviorOptions } from "@paperbits/common/behaviors/behavior.align";
23

34
ko.bindingHandlers["alignment"] = {
45
init: (arrowElement: HTMLElement, valueAccessor) => {
56
const config = ko.unwrap(valueAccessor());
67
const alignment = arrowElement.getAttribute("alignment");
78

9+
// Attach the behavior for click handling
10+
const behaviorOptions: AlignBehaviorOptions = {
11+
onAlign: () => {
12+
if (config.onChange) {
13+
config.onChange(alignment);
14+
}
15+
}
16+
};
17+
const behaviorHandle = AlignBehavior.attach(arrowElement, behaviorOptions);
18+
19+
// Apply CSS binding for active state
820
ko.applyBindingsToNode(arrowElement, {
9-
click: () => {
10-
config.onChange(alignment);
11-
},
1221
css: {
13-
active: ko.pureComputed(() => config.position() === alignment)
22+
active: ko.pureComputed(() => config.position && config.position() === alignment)
1423
}
1524
}, null);
25+
26+
// Dispose behavior on element disposal
27+
ko.utils.domNodeDisposal.addDisposeCallback(arrowElement, () => {
28+
if (behaviorHandle && behaviorHandle.detach) {
29+
behaviorHandle.detach();
30+
}
31+
});
1632
}
1733
};
Lines changed: 14 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,28 @@
11
import { Events } from "@paperbits/common/events";
22
import * as ko from "knockout";
3-
4-
export interface SliderConfig {
5-
data?: any;
6-
percentage?: number;
7-
offset?: number;
8-
onChange: (viewModel: any, percentage: number) => void;
9-
}
3+
import { AngleBehavior, AngleBehaviorOptions } from "@paperbits/common/behaviors/behavior.angle";
104

115
ko.bindingHandlers["angle"] = {
12-
init: (element: HTMLElement, valueAccessor: () => any) => {
13-
const config = valueAccessor();
14-
const angleObservable = config;
15-
const rect = element.getBoundingClientRect();
16-
const centerX = Math.floor(rect.width / 2);
17-
const centerY = Math.floor(rect.height / 2);
18-
let tracking = false;
6+
init: (element: HTMLElement, valueAccessor: () => ko.Observable<number>) => {
7+
const angleObservable = valueAccessor();
198

20-
const determineAngle = (x: number, y: number) => {
21-
const dx = centerX - x;
22-
const dy = centerY - y;
23-
let theta = Math.atan2(dy, dx) * 180 / Math.PI;
24-
theta += -90;
9+
if (!ko.isObservable(angleObservable)) {
10+
console.warn("Angle binding handler expects an observable.");
11+
return;
12+
}
2513

26-
if (theta < 0) {
27-
theta += 360;
14+
const behaviorOptions: AngleBehaviorOptions = {
15+
onChange: (newAngle: number) => {
16+
angleObservable(newAngle);
2817
}
29-
30-
angleObservable(Math.floor(theta));
31-
};
32-
33-
const onMouseDown = (event: MouseEvent) => {
34-
tracking = true;
35-
determineAngle(event.offsetX, event.offsetY);
3618
};
3719

38-
const onMouseUp = (event: MouseEvent) => {
39-
tracking = false;
40-
determineAngle(event.offsetX, event.offsetY);
41-
};
42-
43-
const onMouseMove = (event: MouseEvent) => {
44-
if (!tracking) {
45-
return;
46-
}
47-
48-
determineAngle(event.offsetX, event.offsetY);
49-
};
50-
51-
element.addEventListener(Events.MouseDown, onMouseDown);
52-
element.addEventListener(Events.MouseUp, onMouseUp, true);
53-
element.addEventListener(Events.MouseMove, onMouseMove, true);
20+
const behaviorHandle = AngleBehavior.attach(element, behaviorOptions);
5421

5522
ko.utils.domNodeDisposal.addDisposeCallback(element, () => {
56-
element.removeEventListener(Events.MouseDown, onMouseDown);
57-
element.removeEventListener(Events.MouseUp, onMouseUp, true);
58-
element.removeEventListener(Events.MouseMove, onMouseMove, true);
23+
if (behaviorHandle && behaviorHandle.detach) {
24+
behaviorHandle.detach();
25+
}
5926
});
6027
}
6128
};
Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
11
import * as ko from "knockout";
2+
import { Attr2wayBehavior, Attr2wayConfig } from "@paperbits/common/behaviors/behavior.attr2way";
23

34
ko.bindingHandlers["attr2way"] = {
45
init: (element: HTMLElement, valueAccessor: () => any) => {
5-
const config = valueAccessor();
6-
const attributeNames = Object.keys(config);
7-
8-
const callback: MutationCallback = (mutations: MutationRecord[], observer: MutationObserver) => {
9-
for (const mutation of mutations) {
10-
if (mutation.type === "attributes" && attributeNames.includes(mutation.attributeName)) {
11-
const value = element.getAttribute(mutation.attributeName);
12-
13-
const observable = config[mutation.attributeName];
6+
const originalConfig = valueAccessor();
7+
const attributeNames = Object.keys(originalConfig);
8+
9+
// The behavior needs a way to update the Knockout observables.
10+
// We create a new config that maps attribute names to functions that update the corresponding observable.
11+
const behaviorConfig: Attr2wayConfig = {};
1412

13+
for (const attrName of attributeNames) {
14+
const observable = originalConfig[attrName];
15+
if (ko.isObservable(observable)) {
16+
behaviorConfig[attrName] = (value: string | null) => {
1517
if (observable() !== value) {
1618
observable(value);
1719
}
18-
}
20+
};
1921
}
20-
};
22+
}
2123

22-
const observer = new MutationObserver(callback);
23-
24-
observer.observe(element, { attributes: true });
24+
const behaviorHandle = Attr2wayBehavior.attach(element, behaviorConfig);
2525

2626
ko.utils.domNodeDisposal.addDisposeCallback(element, () => {
27-
observer.disconnect();
27+
if (behaviorHandle && behaviorHandle.detach) {
28+
behaviorHandle.detach();
29+
}
2830
});
2931
}
3032
};

src/ko/bindingHandlers/bindingHandlers.background.ts

Lines changed: 36 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,54 @@
1-
import { StyleService } from "@paperbits/styles";
2-
import * as ko from "knockout";
1+
import * as ko from "knockout";
32
import { BackgroundModel } from "@paperbits/common/widgets/background";
3+
import { BackgroundBehavior, BehaviorHandle } from "@paperbits/common/behaviors/behavior.background";
44

5-
ko.bindingHandlers["style"] = {
6-
update(element: HTMLElement, valueAccessor): void {
7-
const value = ko.utils.unwrapObservable(valueAccessor() || {});
8-
9-
ko.utils.objectForEach(value, function (styleName, styleValue) {
10-
styleValue = ko.utils.unwrapObservable(styleValue);
11-
12-
if (styleValue === null || styleValue === undefined || styleValue === false) {
13-
// Empty string removes the value, whereas null/undefined have no effect
14-
styleValue = "";
15-
}
16-
17-
element.style.setProperty(styleName, styleValue);
18-
});
19-
}
20-
};
5+
// ko.bindingHandlers["style"] = { ... }; // This global style binding handler remains unchanged.
216

227
export class BackgroundBindingHandler {
23-
constructor(styleService: StyleService) {
8+
constructor() { // StyleService removed as it was not used by this specific binding handler
249
ko.bindingHandlers["background"] = {
25-
init(element: HTMLElement, valueAccessor: () => BackgroundModel): void {
26-
const configuration = valueAccessor();
27-
const styleObservable = ko.observable();
28-
29-
const setBackground = async (backgroundModel: BackgroundModel) => {
30-
if (backgroundModel.sourceUrl) {
31-
styleObservable({
32-
"background-image": `url("${ko.unwrap(backgroundModel.sourceUrl)}")`,
33-
"background-repeat": "no-repeat",
34-
"background-size": "cover",
35-
"background-position": "center",
36-
"background-color": backgroundModel.color
37-
});
10+
init(element: HTMLElement, valueAccessor: () => BackgroundModel | ko.Observable<BackgroundModel>): void {
11+
const configurationObservableOrModel = valueAccessor();
12+
let behaviorHandle: BehaviorHandle | undefined;
13+
14+
// Helper to unwrap BackgroundModel properties if they are observable
15+
// This ensures the Behavior class receives plain data.
16+
const getCleanModel = (model?: BackgroundModel): BackgroundModel => {
17+
if (!model) {
18+
return {};
3819
}
39-
else if (backgroundModel.color) {
40-
styleObservable({
41-
"background-color": backgroundModel.color
42-
});
20+
const cleanModel: BackgroundModel = {};
21+
if (model.sourceUrl !== undefined) {
22+
cleanModel.sourceUrl = ko.unwrap(model.sourceUrl);
4323
}
44-
else {
45-
styleObservable({
46-
"background-image": null,
47-
"background-repeat": null,
48-
"background-size": null,
49-
"background-position": null,
50-
"background-color": null
51-
});
24+
if (model.color !== undefined) {
25+
cleanModel.color = ko.unwrap(model.color);
5226
}
27+
// Add other properties from BackgroundModel if they can be observable and are used
28+
return cleanModel;
5329
};
5430

55-
ko.applyBindingsToNode(element, { style: styleObservable }, null);
31+
if (ko.isObservable(configurationObservableOrModel)) {
32+
const configurationObservable = configurationObservableOrModel as ko.Observable<BackgroundModel>;
33+
34+
const initialModel = getCleanModel(ko.unwrap(configurationObservable));
35+
behaviorHandle = BackgroundBehavior.attach(element, initialModel);
5636

57-
if (ko.isObservable(configuration)) {
58-
configuration.subscribe((newConfiguration) => {
59-
if (!newConfiguration) {
60-
setBackground({});
61-
}
62-
else {
63-
setBackground(ko.unwrap(newConfiguration));
37+
configurationObservable.subscribe((newConfiguration) => {
38+
if (behaviorHandle?.update) {
39+
behaviorHandle.update(getCleanModel(newConfiguration));
6440
}
6541
});
42+
} else {
43+
const model = getCleanModel(configurationObservableOrModel as BackgroundModel);
44+
behaviorHandle = BackgroundBehavior.attach(element, model);
6645
}
6746

68-
let initialConfiguration = ko.unwrap(configuration);
69-
70-
if (!initialConfiguration) {
71-
initialConfiguration = {};
72-
}
73-
74-
setBackground(initialConfiguration);
47+
ko.utils.domNodeDisposal.addDisposeCallback(element, () => {
48+
if (behaviorHandle?.dispose) {
49+
behaviorHandle.dispose();
50+
}
51+
});
7552
}
7653
};
7754
}

src/ko/bindingHandlers/bindingHandlers.hyperlink.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,24 @@ import { HyperlinkBehavior } from "@paperbits/common/behaviors/behavior.hyperlin
55
ko.bindingHandlers["hyperlink"] = {
66
update(element: HTMLElement, valueAccessor: () => HyperlinkModel): void {
77
const hyperlink: HyperlinkModel = valueAccessor();
8-
const behavior = new HyperlinkBehavior();
8+
let behaviorHandle: any;
99

1010
if (ko.isObservable(hyperlink)) {
11-
hyperlink.subscribe(newValue => behavior.attach(element, newValue));
11+
hyperlink.subscribe(newValue => {
12+
if (behaviorHandle && behaviorHandle.detach) {
13+
behaviorHandle.detach();
14+
}
15+
behaviorHandle = HyperlinkBehavior.attach(element, newValue);
16+
});
1217
}
1318

1419
const initial = ko.unwrap(hyperlink);
15-
behavior.attach(element, initial);
20+
behaviorHandle = HyperlinkBehavior.attach(element, initial);
21+
22+
if (behaviorHandle && behaviorHandle.detach) {
23+
ko.utils.domNodeDisposal.addDisposeCallback(element, () => {
24+
behaviorHandle.detach();
25+
});
26+
}
1627
}
1728
};

src/ko/bindingHandlers/bindingHandlers.listbox.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,24 @@ import { ListboxBehavior, ListboxOptions } from "@paperbits/common/behaviors/beh
33

44
ko.bindingHandlers["listbox"] = {
55
init: (listboxElement: HTMLElement, valueAccessor: () => ListboxOptions) => {
6-
const behaviorHandle = ListboxBehavior.attach(listboxElement, valueAccessor());
6+
const originalOptions = valueAccessor();
7+
8+
// Adapt the onSelect callback to maintain the original contract (passing ko.dataFor(element))
9+
// while the behavior itself now passes the HTMLElement.
10+
const adaptedOptions: ListboxOptions = {
11+
onSelect: (selectedElement: HTMLElement) => {
12+
if (originalOptions && originalOptions.onSelect) {
13+
originalOptions.onSelect(ko.dataFor(selectedElement));
14+
}
15+
}
16+
};
17+
18+
const behaviorHandle = ListboxBehavior.attach(listboxElement, adaptedOptions);
719

820
ko.utils.domNodeDisposal.addDisposeCallback(listboxElement, (): void => {
9-
behaviorHandle.detach();
21+
if (behaviorHandle && behaviorHandle.detach) {
22+
behaviorHandle.detach();
23+
}
1024
});
1125
}
1226
};
Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
import * as ko from "knockout";
2-
import { MarkdownBehavior } from "@paperbits/common/behaviors/bahavior.markdown";
2+
import { MarkdownBehavior } from "@paperbits/common/behaviors/behavior.markdown"; // Corrected path
33

44
ko.bindingHandlers["markdown"] = {
5-
update: async (element: HTMLElement, valueAccessor: () => string): Promise<void> => {
5+
update: (element: HTMLElement, valueAccessor: () => string): void => {
66
const markdown = ko.unwrap(valueAccessor());
7-
const behavior = new MarkdownBehavior();
7+
8+
// Clean up any previous behavior instance before attaching a new one
9+
const existingHandle = ko.utils.domData.get(element, "markdownBehaviorHandle");
10+
if (existingHandle && existingHandle.dispose) {
11+
existingHandle.dispose();
12+
}
813

9-
behavior.attach(element, markdown);
14+
const behaviorHandle = MarkdownBehavior.attach(element, markdown);
15+
ko.utils.domData.set(element, "markdownBehaviorHandle", behaviorHandle); // Store the handle for potential cleanup
16+
17+
// Ensure dispose is called when the element is removed
18+
ko.utils.domNodeDisposal.addDisposeCallback(element, () => {
19+
if (behaviorHandle && behaviorHandle.detach) {
20+
behaviorHandle.detach();
21+
}
22+
});
1023
}
1124
};

0 commit comments

Comments
 (0)