+ parentNode = sourceNode;
+ }
+ ko.utils.arrayForEach(ko.virtualElements.childNodes(parentNode), function (child) {
+ // FIXME - This cloneNode could be expensive; we may prefer to iterate over the
+ // parentNode children in reverse (so as not to foul the indexes as childNodes are
+ // removed from parentNode when inserted into the container)
+ if (child) {
+ container.insertBefore(child.cloneNode(true), null);
+ }
+ });
+ return container;
+}
+
+// Mimic a KO change item 'add'
+function valueToChangeAddItem(value, index) {
+ return {
+ status: "added",
+ value: value,
+ index: index
+ };
+}
+
+// KO 3.4 doesn't seem to export this utility function so it's here just to be sure
+function createSymbolOrString(identifier) {
+ return typeof Symbol === "function" ? Symbol(identifier) : identifier;
+}
+
+// store a symbol for caching the pending delete info index in the data item objects
+const PENDING_DELETE_INDEX_KEY = createSymbolOrString("_ko_ffe_pending_delete_index");
+
+function FastForEach(spec) {
+ this.element = spec.element;
+ this.container = isVirtualNode(this.element) ?
+ this.element.parentNode : this.element;
+ this.$context = spec.$context;
+ this.data = spec.data;
+ this.as = spec.as;
+ this.noContext = spec.noContext;
+ this.noIndex = spec.noIndex;
+ this.afterAdd = spec.afterAdd;
+ this.beforeRemove = spec.beforeRemove;
+ this.templateNode = makeTemplateNode(
+ spec.templateNode || (spec.name ? document.getElementById(spec.name).cloneNode(true) : spec.element)
+ );
+ this.afterQueueFlush = spec.afterQueueFlush;
+ this.beforeQueueFlush = spec.beforeQueueFlush;
+ this.changeQueue = [];
+ this.firstLastNodesList = [];
+ this.indexesToDelete = [];
+ this.rendering_queued = false;
+ this.pendingDeletes = [];
+
+ // Remove existing content.
+ ko.virtualElements.emptyNode(this.element);
+
+ // Prime content
+ const primeData = ko.unwrap(this.data);
+ if (primeData && primeData.map) {
+ this.onArrayChange(primeData.map(valueToChangeAddItem), true);
+ }
+
+ // Watch for changes
+ if (ko.isObservable(this.data)) {
+ if (!this.data.indexOf) {
+ // Make sure the observable is trackable.
+ this.data = this.data.extend({ trackArrayChanges: true });
+ }
+ this.changeSubs = this.data.subscribe(this.onArrayChange, this, "arrayChange");
+ }
+}
+
+FastForEach.PENDING_DELETE_INDEX_KEY = PENDING_DELETE_INDEX_KEY;
+
+FastForEach.animateFrame = window.requestAnimationFrame || function (cb) { return window.setTimeout(cb, 1000 / 60); };
+
+
+FastForEach.prototype.dispose = function () {
+ if (this.changeSubs) {
+ this.changeSubs.dispose();
+ }
+ this.flushPendingDeletes();
+};
+
+
+// If the array changes we register the change.
+FastForEach.prototype.onArrayChange = function (changeSet, isInitial) {
+ const self = this;
+ const changeMap = {
+ added: [],
+ deleted: []
+ };
+
+ // knockout array change notification index handling:
+ // - sends the original array indexes for deletes
+ // - sends the new array indexes for adds
+ // - sorts them all by index in ascending order
+ // because of this, when checking for possible batch additions, any delete can be between to adds with neighboring indexes, so only additions should be checked
+ for (let i = 0, len = changeSet.length; i < len; i++) {
+
+ if (changeMap.added.length && changeSet[i].status == "added") {
+ let lastAdd = changeMap.added[changeMap.added.length - 1];
+ const lastIndex = lastAdd.isBatch ? lastAdd.index + lastAdd.values.length - 1 : lastAdd.index;
+ if (lastIndex + 1 == changeSet[i].index) {
+ if (!lastAdd.isBatch) {
+ // transform the last addition into a batch addition object
+ lastAdd = {
+ isBatch: true,
+ status: "added",
+ index: lastAdd.index,
+ values: [lastAdd.value]
+ };
+ changeMap.added.splice(changeMap.added.length - 1, 1, lastAdd);
+ }
+ lastAdd.values.push(changeSet[i].value);
+ continue;
+ }
+ }
+
+ changeMap[changeSet[i].status].push(changeSet[i]);
+ }
+
+ if (changeMap.deleted.length > 0) {
+ this.changeQueue.push.apply(this.changeQueue, changeMap.deleted);
+ this.changeQueue.push({ status: "clearDeletedIndexes" });
+ }
+ this.changeQueue.push.apply(this.changeQueue, changeMap.added);
+ // Once a change is registered, the ticking count-down starts for the processQueue.
+ if (this.changeQueue.length > 0 && !this.rendering_queued) {
+ this.rendering_queued = true;
+ if (isInitial) {
+ self.processQueue();
+ } else {
+ FastForEach.animateFrame.call(window, function () { self.processQueue(); });
+ }
+ }
+};
+
+
+// Reflect all the changes in the queue in the DOM, then wipe the queue.
+FastForEach.prototype.processQueue = function () {
+ const self = this;
+ let lowestIndexChanged = MAX_LIST_SIZE;
+
+ // Callback so folks can do things before the queue flush.
+ if (typeof this.beforeQueueFlush === "function") {
+ this.beforeQueueFlush(this.changeQueue);
+ }
+
+ ko.utils.arrayForEach(this.changeQueue, function (changeItem) {
+ if (typeof changeItem.index === "number") {
+ lowestIndexChanged = Math.min(lowestIndexChanged, changeItem.index);
+ }
+ // console.log(self.data(), "CI", JSON.stringify(changeItem, null, 2), JSON.stringify($(self.element).text()))
+ self[changeItem.status](changeItem);
+ // console.log(" ==> ", JSON.stringify($(self.element).text()))
+ });
+ this.flushPendingDeletes();
+ this.rendering_queued = false;
+
+ // Update our indexes.
+ if (!this.noIndex) {
+ this.updateIndexes(lowestIndexChanged);
+ }
+
+ // Callback so folks can do things.
+ if (typeof this.afterQueueFlush === "function") {
+ this.afterQueueFlush(this.changeQueue);
+ }
+ this.changeQueue = [];
+};
+
+
+function extendWithIndex(context) {
+ context.$index = ko.observable();
+}
+
+
+// Process a changeItem with {status: 'added', ...}
+FastForEach.prototype.added = function (changeItem) {
+ const index = changeItem.index;
+ const valuesToAdd = changeItem.isBatch ? changeItem.values : [changeItem.value];
+ const referenceElement = this.getLastNodeBeforeIndex(index);
+ // gather all childnodes for a possible batch insertion
+ const allChildNodes = [];
+
+ for (let i = 0, len = valuesToAdd.length; i < len; ++i) {
+ let childNodes;
+
+ // we check if we have a pending delete with reusable nodesets for this data, and if yes, we reuse one nodeset
+ const pendingDelete = this.getPendingDeleteFor(valuesToAdd[i]);
+ if (pendingDelete && pendingDelete.nodesets.length) {
+ childNodes = pendingDelete.nodesets.pop();
+ } else {
+ const templateClone = this.templateNode.cloneNode(true);
+ let childContext;
+
+ if (this.noContext) {
+ childContext = this.$context.extend({
+ $item: valuesToAdd[i],
+ $index: this.noIndex ? undefined : ko.observable()
+ });
+ } else {
+ childContext = this.$context.createChildContext(valuesToAdd[i], this.as || null, this.noIndex ? undefined : extendWithIndex);
+ }
+
+ // apply bindings first, and then process child nodes, because bindings can add childnodes
+ ko.applyBindingsToDescendants(childContext, templateClone);
+
+ childNodes = ko.virtualElements.childNodes(templateClone);
+ }
+
+ // Note discussion at https://github.com/angular/angular.js/issues/7851
+ allChildNodes.push.apply(allChildNodes, Array.prototype.slice.call(childNodes));
+ this.firstLastNodesList.splice(index + i, 0, { first: childNodes[0], last: childNodes[childNodes.length - 1] });
+ }
+
+ if (typeof this.afterAdd === "function") {
+ this.afterAdd({
+ nodeOrArrayInserted: this.insertAllAfter(allChildNodes, referenceElement),
+ foreachInstance: this
+ }
+ );
+ } else {
+ this.insertAllAfter(allChildNodes, referenceElement);
+ }
+};
+
+FastForEach.prototype.getNodesForIndex = function (index) {
+ const result = [];
+ let ptr = this.firstLastNodesList[index].first;
+ const last = this.firstLastNodesList[index].last;
+ result.push(ptr);
+ while (ptr && ptr !== last) {
+ ptr = ptr.nextSibling;
+ result.push(ptr);
+ }
+ return result;
+};
+
+FastForEach.prototype.getLastNodeBeforeIndex = function (index) {
+ if (index < 1 || index - 1 >= this.firstLastNodesList.length)
+ return null;
+ return this.firstLastNodesList[index - 1].last;
+};
+
+FastForEach.prototype.insertAllAfter = function (nodeOrNodeArrayToInsert, insertAfterNode) {
+ let frag, len, i;
+ const containerNode = this.element;
+
+ // poor man's node and array check, should be enough for this
+ if (nodeOrNodeArrayToInsert.nodeType === undefined && nodeOrNodeArrayToInsert.length === undefined) {
+ throw new Error("Expected a single node or a node array");
+ }
+ if (nodeOrNodeArrayToInsert.nodeType !== undefined) {
+ ko.virtualElements.insertAfter(containerNode, nodeOrNodeArrayToInsert, insertAfterNode);
+ return [nodeOrNodeArrayToInsert];
+ } else if (nodeOrNodeArrayToInsert.length === 1) {
+ ko.virtualElements.insertAfter(containerNode, nodeOrNodeArrayToInsert[0], insertAfterNode);
+ } else if (supportsDocumentFragment) {
+ frag = document.createDocumentFragment();
+
+ for (i = 0, len = nodeOrNodeArrayToInsert.length; i !== len; ++i) {
+ frag.appendChild(nodeOrNodeArrayToInsert[i]);
+ }
+ ko.virtualElements.insertAfter(containerNode, frag, insertAfterNode);
+ } else {
+ // Nodes are inserted in reverse order - pushed down immediately after
+ // the last node for the previous item or as the first node of element.
+ for (i = nodeOrNodeArrayToInsert.length - 1; i >= 0; --i) {
+ const child = nodeOrNodeArrayToInsert[i];
+ if (!child) { break; }
+ ko.virtualElements.insertAfter(containerNode, child, insertAfterNode);
+ }
+ }
+ return nodeOrNodeArrayToInsert;
+};
+
+// checks if the deleted data item should be handled with delay for a possible reuse at additions
+FastForEach.prototype.shouldDelayDeletion = function (data) {
+ return data && (typeof data === "object" || typeof data === "function");
+};
+
+// gets the pending deletion info for this data item
+FastForEach.prototype.getPendingDeleteFor = function (data) {
+ const index = data && data[PENDING_DELETE_INDEX_KEY];
+ if (index === undefined) return null;
+ return this.pendingDeletes[index];
+};
+
+// tries to find the existing pending delete info for this data item, and if it can't, it registers one
+FastForEach.prototype.getOrCreatePendingDeleteFor = function (data) {
+ let pd = this.getPendingDeleteFor(data);
+ if (pd) {
+ return pd;
+ }
+ pd = {
+ data: data,
+ nodesets: []
+ };
+ data[PENDING_DELETE_INDEX_KEY] = this.pendingDeletes.length;
+ this.pendingDeletes.push(pd);
+ return pd;
+};
+
+// Process a changeItem with {status: 'deleted', ...}
+FastForEach.prototype.deleted = function (changeItem) {
+ // if we should delay the deletion of this data, we add the nodeset to the pending delete info object
+ if (this.shouldDelayDeletion(changeItem.value)) {
+ const pd = this.getOrCreatePendingDeleteFor(changeItem.value);
+ pd.nodesets.push(this.getNodesForIndex(changeItem.index));
+ } else { // simple data, just remove the nodes
+ this.removeNodes(this.getNodesForIndex(changeItem.index));
+ }
+ this.indexesToDelete.push(changeItem.index);
+};
+
+// removes a set of nodes from the DOM
+FastForEach.prototype.removeNodes = function (nodes) {
+ if (!nodes.length) { return; }
+
+ const removeFn = function () {
+ const parent = nodes[0].parentNode;
+ for (let i = nodes.length - 1; i >= 0; --i) {
+ ko.cleanNode(nodes[i]);
+ parent.removeChild(nodes[i]);
+ }
+ };
+
+ if (this.beforeRemove) {
+ const beforeRemoveReturn = this.beforeRemove({
+ nodesToRemove: nodes, foreachInstance: this
+ }) || {};
+ // If beforeRemove returns a `then`âable e.g. a Promise, we remove
+ // the nodes when that thenable completes. We pass any errors to
+ // ko.onError.
+ if (typeof beforeRemoveReturn.then === "function") {
+ beforeRemoveReturn.then(removeFn, ko.onError ? ko.onError : undefined);
+ }
+ } else {
+ removeFn();
+ }
+};
+
+// flushes the pending delete info store
+// this should be called after queue processing has finished, so that data items and remaining (not reused) nodesets get cleaned up
+// we also call it on dispose not to leave any mess
+FastForEach.prototype.flushPendingDeletes = function () {
+ for (let i = 0, len = this.pendingDeletes.length; i != len; ++i) {
+ const pd = this.pendingDeletes[i];
+ while (pd.nodesets.length) {
+ this.removeNodes(pd.nodesets.pop());
+ }
+ if (pd.data && pd.data[PENDING_DELETE_INDEX_KEY] !== undefined)
+ delete pd.data[PENDING_DELETE_INDEX_KEY];
+ }
+ this.pendingDeletes = [];
+};
+
+// We batch our deletion of item indexes in our parallel array.
+// See brianmhunt/knockout-fast-foreach#6/#8
+FastForEach.prototype.clearDeletedIndexes = function () {
+ // We iterate in reverse on the presumption (following the unit tests) that KO's diff engine
+ // processes diffs (esp. deletes) monotonically ascending i.e. from index 0 -> N.
+ for (let i = this.indexesToDelete.length - 1; i >= 0; --i) {
+ this.firstLastNodesList.splice(this.indexesToDelete[i], 1);
+ }
+ this.indexesToDelete = [];
+};
+
+
+FastForEach.prototype.getContextStartingFrom = function (node) {
+ let ctx;
+ while (node) {
+ ctx = ko.contextFor(node);
+ if (ctx) { return ctx; }
+ node = node.nextSibling;
+ }
+};
+
+
+FastForEach.prototype.updateIndexes = function (fromIndex) {
+ let ctx;
+ for (let i = fromIndex, len = this.firstLastNodesList.length; i < len; ++i) {
+ ctx = this.getContextStartingFrom(this.firstLastNodesList[i].first);
+ if (ctx) { ctx.$index(i); }
+ }
+};
+
+
+ko.bindingHandlers["fastForEach"] = {
+ // Valid valueAccessors:
+ // []
+ // ko.observable([])
+ // ko.observableArray([])
+ // ko.computed
+ // {data: array, name: string, as: string}
+ init: function init(element, valueAccessor, bindings, vm, context) {
+ let ffe;
+ const value = valueAccessor();
+ if (isPlainObject(value)) {
+ value.element = value.element || element;
+ value.$context = context;
+ ffe = new FastForEach(value);
+ } else {
+ ffe = new FastForEach({
+ element: element,
+ data: ko.unwrap(context.$rawData) === value ? context.$rawData : value,
+ $context: context
+ });
+ }
+
+ ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
+ ffe.dispose();
+ });
+ return { controlsDescendantBindings: true };
+ }
+};
+
+ko.virtualElements.allowedBindings.fastForEach = true;
\ No newline at end of file
diff --git a/src/bindingHandlers/mapChart.ts b/src/bindingHandlers/mapChart.ts
index 5c4a52141..c3782f3fb 100644
--- a/src/bindingHandlers/mapChart.ts
+++ b/src/bindingHandlers/mapChart.ts
@@ -44,7 +44,7 @@ ko.bindingHandlers["mapChart"] = {
const path = d3.geoPath().projection(projection);
const svg = d3.select(element).append("svg");
-
+
const chartTitle = allBindings[chartTitleAttribute];
const chartDesc = allBindings[chartDescAttribute];
@@ -94,20 +94,20 @@ ko.bindingHandlers["mapChart"] = {
// .attr("class", "graticule")
// .attr("d", path);
- const onMouseOver = (datum, index): void => {
- d3.selectAll(".subunit-label.la" + datum.id + datum.properties.name.replace(/[ \.#']+/g, ""))
+ const onMouseOver = (event, datum): void => {
+ d3.selectAll(".subunit-label.la" + element.id + datum.id + datum.properties.name.replace(/[ \.#']+/g, ""))
.style("display", "inline-block");
- d3.selectAll(".subunit.ca" + datum.id)
+ d3.selectAll(".subunit.ca" + element.id + datum.id)
.style("stroke", "#8e8e8e")
.style("stroke-width", "1px");
};
- const onMouseOut = (datum: any, index: number): void => {
- d3.selectAll(".subunit-label.la" + datum.id + datum.properties.name.replace(/[ \.#']+/g, ""))
+ const onMouseOut = (event: any, datum): void => {
+ d3.selectAll(".subunit-label.la" + element.id + datum.id + datum.properties.name.replace(/[ \.#']+/g, ""))
.style("display", "none");
- d3.selectAll(".subunit.ca" + datum.id)
+ d3.selectAll(".subunit.ca" + element.id + datum.id)
.style("fill", heatColor(datum))
.style("stroke", "none");
};
@@ -160,10 +160,10 @@ ko.bindingHandlers["mapChart"] = {
const subunits = topojson.feature(worldMapShapes, worldMapShapes.objects.subunits);
subunits.features = subunits.features.filter((d) => d.id !== "ATA");
-
+
minHeat = d3.min(subunits.features, (d) => d.properties.heat);
const maxHeat = d3.max(subunits.features, (d) => d.properties.heat);
-
+
let heats = subunits.features.map((d) => { return d.properties.heat; });
heats = heats.filter((d) => d).sort(d3.ascending);
@@ -173,14 +173,14 @@ ko.bindingHandlers["mapChart"] = {
.data(subunits.features).enter();
countries.insert("path", ".graticule")
- .attr("class", (record) => "subunit ca" + record.id)
+ .attr("class", (record) => "subunit ca" + element.id + record.id)
.style("fill", heatColor)
.attr("d", path)
.on("mouseover", onMouseOver)
.on("mouseout", onMouseOut);
countries.append("text")
- .attr("class", (record) => "subunit-label la" + record.id + record.properties.name.replace(/[ \.#']+/g, ""))
+ .attr("class", (record) => "subunit-label la" + element.id + record.id + record.properties.name.replace(/[ \.#']+/g, ""))
.attr("transform", (record) => "translate(" + (width - (5 * record.properties.name.length)) + "," + (15) + ")")
.attr("dy", ".35em")
// .attr("filter", "url(#tooltip)")
diff --git a/src/bindingHandlers/markdown.ts b/src/bindingHandlers/markdown.ts
index e39c9d6d4..8a2668282 100644
--- a/src/bindingHandlers/markdown.ts
+++ b/src/bindingHandlers/markdown.ts
@@ -37,7 +37,7 @@ ko.bindingHandlers["markdown"] = {
ko.applyBindingsToNode(element, { html: htmlObservable }, null);
- const html = markdownService.processMarkdown(markdown);
+ const html = markdownService.processMarkdown(markdown, length);
htmlObservable(html);
}
};
\ No newline at end of file
diff --git a/src/bindingHandlers/minMaxAvgChart.ts b/src/bindingHandlers/minMaxAvgChart.ts
index 6590d69bc..66f8774ca 100644
--- a/src/bindingHandlers/minMaxAvgChart.ts
+++ b/src/bindingHandlers/minMaxAvgChart.ts
@@ -20,7 +20,7 @@ ko.bindingHandlers["minMaxAvgChart"] = {
const minMaxAreaColor = "#ddd";
const avgLineColor = "#1786d8";
- const dimensions = [{
+ const dimensions = [{
displayName: "Max",
key: "max",
color: minMaxAreaColor
@@ -127,9 +127,6 @@ ko.bindingHandlers["minMaxAvgChart"] = {
.attr("fill", "none")
.attr("stroke-width", 2);
-
-
-
for (let i = 0; i < dimensions.length; i++) {
const dimension = dimensions[i];
@@ -204,8 +201,8 @@ ko.bindingHandlers["minMaxAvgChart"] = {
verticalLine.style("opacity", "1");
verticalLineTooltip.style("opacity", "1");
})
- .on("mousemove", () => { // mouse moving over canvas
- const mouse = d3.mouse(chartSvgNode);
+ .on("mousemove", (event) => { // mouse moving over canvas
+ const mouse = d3.pointer(event, chartSvgNode);
const mouseX = mouse[0];
verticalLine
@@ -250,7 +247,7 @@ ko.bindingHandlers["minMaxAvgChart"] = {
}
}
- verticalLineTooltipText.text(formatXDetailed(timestamp))
+ verticalLineTooltipText.text(formatXDetailed(timestamp));
});
diff --git a/src/bindingHandlers/syntaxHighlight.ts b/src/bindingHandlers/syntaxHighlight.ts
index 5d6185899..5dc765996 100644
--- a/src/bindingHandlers/syntaxHighlight.ts
+++ b/src/bindingHandlers/syntaxHighlight.ts
@@ -24,7 +24,7 @@ interface SyntaxHighlightConfig {
ko.bindingHandlers["syntaxHighlight"] = {
update: (element: HTMLElement, valueAccessor: () => SyntaxHighlightConfig): void => {
const config = valueAccessor();
- let code = ko.unwrap(config.code);
+ let code = ko.unwrap(config.code) || "";
const language = ko.unwrap(config.language);
const render = async () => {
@@ -56,7 +56,7 @@ ko.bindingHandlers["syntaxHighlight"] = {
highlightLanguage = "ruby";
break;
case "swift":
- highlightLanguage = "swift"
+ highlightLanguage = "swift";
break;
case "xml":
highlightLanguage = "xml";
@@ -76,8 +76,11 @@ ko.bindingHandlers["syntaxHighlight"] = {
ko.applyBindingsToNode(element, { text: text }, null);
}
else {
-
- code = code.replaceAll("/", "fwdslsh"); // workaround for PrismJS bug.
+ if (typeof code === "string") {
+ code = code.replaceAll("/", "fwdslsh"); // workaround for PrismJS bug.
+ } else {
+ code = "";
+ }
let html = Prism.highlight(code, Prism.languages[highlightLanguage], highlightLanguage);
html = html.replaceAll("fwdslsh", "/");
diff --git a/src/components/apis/api-products/apiProductsContract.ts b/src/components/apis/api-products/apiProductsContract.ts
index 71816bfc6..745b17f88 100644
--- a/src/components/apis/api-products/apiProductsContract.ts
+++ b/src/components/apis/api-products/apiProductsContract.ts
@@ -6,7 +6,7 @@ import { HyperlinkContract } from "@paperbits/common/editing";
*/
export interface ApiProductsContract extends Contract {
/**
- * List layout. "list" or "tiles"
+ * List layout. "list", "dropdown" or "tiles"
*/
itemStyleView?: string;
diff --git a/src/components/apis/api-products/apiProductsHandlers.ts b/src/components/apis/api-products/apiProductsHandlers.ts
index 1c1bd22e1..788e75ee3 100644
--- a/src/components/apis/api-products/apiProductsHandlers.ts
+++ b/src/components/apis/api-products/apiProductsHandlers.ts
@@ -1,32 +1,20 @@
-īģŋimport { IWidgetOrder, IWidgetHandler } from "@paperbits/common/editing";
+īģŋimport { IWidgetHandler } from "@paperbits/common/editing";
import { ApiProductsModel } from "./apiProductsModel";
export class ApiProductsHandlers implements IWidgetHandler {
- public async getWidgetOrder(): Promise
{
- const widgetOrder: IWidgetOrder = {
- name: "api-products",
- category: "APIs",
- displayName: "API: Products",
- iconClass: "widget-icon widget-icon-api-management",
- requires: ["html"],
- createModel: async () => new ApiProductsModel("list")
- };
+ public async getWidgetModel(): Promise {
+ return new ApiProductsModel("list");
+ }
+}
- return widgetOrder;
+export class ApiProductsDropdownHandlers implements IWidgetHandler {
+ public async getWidgetModel(): Promise {
+ return new ApiProductsModel("dropdown");
}
}
export class ApiProductsTilesHandlers implements IWidgetHandler {
- public async getWidgetOrder(): Promise {
- const widgetOrder: IWidgetOrder = {
- name: "api-products-tiles",
- category: "APIs",
- displayName: "API: Products (tiles)",
- iconClass: "widget-icon widget-icon-api-management",
- requires: ["html"],
- createModel: async () => new ApiProductsModel("tiles")
- };
-
- return widgetOrder;
+ public async getWidgetModel(): Promise {
+ return new ApiProductsModel("tiles");
}
}
\ No newline at end of file
diff --git a/src/components/apis/api-products/apiProductsModel.ts b/src/components/apis/api-products/apiProductsModel.ts
index ca8a62ade..89ac7c703 100644
--- a/src/components/apis/api-products/apiProductsModel.ts
+++ b/src/components/apis/api-products/apiProductsModel.ts
@@ -5,7 +5,7 @@ import { HyperlinkModel } from "@paperbits/common/permalinks";
*/
export class ApiProductsModel {
/**
- * List layout. "list" or "tiles"
+ * List layout. "list", "dropdown" or "tiles"
*/
public layout?: string;
@@ -14,7 +14,7 @@ export class ApiProductsModel {
*/
public detailsPageHyperlink: HyperlinkModel;
- constructor(layout?: "list"|"tiles") {
+ constructor(layout?: "list"|"dropdown"|"tiles") {
this.layout = layout;
}
}
diff --git a/src/components/apis/api-products/apiProductsModelBinder.ts b/src/components/apis/api-products/apiProductsModelBinder.ts
index 69a42b260..65cd8f044 100644
--- a/src/components/apis/api-products/apiProductsModelBinder.ts
+++ b/src/components/apis/api-products/apiProductsModelBinder.ts
@@ -1,4 +1,3 @@
-import { Contract } from "@paperbits/common";
import { IModelBinder } from "@paperbits/common/editing";
import { IPermalinkResolver } from "@paperbits/common/permalinks";
import { ApiProductsModel } from "./apiProductsModel";
@@ -6,14 +5,6 @@ import { ApiProductsContract } from "./apiProductsContract";
export class ApiProductsModelBinder implements IModelBinder {
constructor(private readonly permalinkResolver: IPermalinkResolver) { }
-
- public canHandleContract(contract: Contract): boolean {
- return contract.type === "api-products";
- }
-
- public canHandleModel(model: Object): boolean {
- return model instanceof ApiProductsModel;
- }
public async contractToModel(contract: ApiProductsContract): Promise {
const model = new ApiProductsModel();
diff --git a/src/components/apis/api-products/ko/apiProducts.html b/src/components/apis/api-products/ko/apiProducts.html
index 41970b2f2..2b6de274f 100644
--- a/src/components/apis/api-products/ko/apiProducts.html
+++ b/src/components/apis/api-products/ko/apiProducts.html
@@ -2,6 +2,10 @@
+
+
+
+
diff --git a/src/components/apis/api-products/ko/apiProducts.module.ts b/src/components/apis/api-products/ko/apiProducts.module.ts
index 8b900d19b..47010e401 100644
--- a/src/components/apis/api-products/ko/apiProducts.module.ts
+++ b/src/components/apis/api-products/ko/apiProducts.module.ts
@@ -1,12 +1,41 @@
-import { IInjectorModule, IInjector } from "@paperbits/common/injection";
+import { IInjector, IInjectorModule } from "@paperbits/common/injection";
+import { IWidgetService } from "@paperbits/common/widgets";
+import { KnockoutComponentBinder } from "@paperbits/core/ko";
+import { ApiProductsModel } from "../apiProductsModel";
import { ApiProductsModelBinder } from "../apiProductsModelBinder";
-import { ApiProductsViewModelBinder } from "./apiProductsViewModelBinder";
import { ApiProductsViewModel } from "./apiProductsViewModel";
+import { ApiProductsViewModelBinder } from "./apiProductsViewModelBinder";
-export class ApiProductsModule implements IInjectorModule {
+
+export class ApiProductsPublishModule implements IInjectorModule {
public register(injector: IInjector): void {
- injector.bind("apiProducts", ApiProductsViewModel);
- injector.bindToCollection("modelBinders", ApiProductsModelBinder);
- injector.bindToCollection("viewModelBinders", ApiProductsViewModelBinder);
+ injector.bindSingleton("apiProductsModelBinder", ApiProductsModelBinder);
+ injector.bindSingleton("apiProductsViewModelBinder", ApiProductsViewModelBinder)
+
+ const widgetService = injector.resolve("widgetService");
+
+ widgetService.registerWidget("apiProducts", {
+ modelDefinition: ApiProductsModel,
+ componentBinder: KnockoutComponentBinder,
+ componentDefinition: ApiProductsViewModel,
+ modelBinder: ApiProductsModelBinder,
+ viewModelBinder: ApiProductsViewModelBinder
+ });
+
+ widgetService.registerWidget("apiProductsDropdown", {
+ modelDefinition: ApiProductsModel,
+ componentBinder: KnockoutComponentBinder,
+ componentDefinition: ApiProductsViewModel,
+ modelBinder: ApiProductsModelBinder,
+ viewModelBinder: ApiProductsViewModelBinder
+ });
+
+ widgetService.registerWidget("apiProductsTiles", {
+ modelDefinition: ApiProductsModel,
+ componentBinder: KnockoutComponentBinder,
+ componentDefinition: ApiProductsViewModel,
+ modelBinder: ApiProductsModelBinder,
+ viewModelBinder: ApiProductsViewModelBinder
+ });
}
}
\ No newline at end of file
diff --git a/src/components/apis/api-products/ko/apiProductsEditor.html b/src/components/apis/api-products/ko/apiProductsEditor.html
index e12164683..de102c016 100644
--- a/src/components/apis/api-products/ko/apiProductsEditor.html
+++ b/src/components/apis/api-products/ko/apiProductsEditor.html
@@ -6,7 +6,7 @@
data-bind="tooltip: 'A link to be navigated on click.'">