Skip to content

add BuilderList, s_donation_button options #4613

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { BuilderList } from "@html_builder/core/building_blocks/builder_list";
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { BuilderButtonGroup } from "./building_blocks/builder_button_group";
import { Dropdown } from "@web/core/dropdown/dropdown";
Expand Down Expand Up @@ -45,6 +46,7 @@ export class BuilderComponentPlugin extends Plugin {
ModelMany2Many,
BuilderDateTimePicker,
BuilderUrlPicker,
BuilderList,
Img,
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<t t-name="html_builder.BuilderButton">
<BuilderComponent>
<button type="button" class="btn" t-att-style="this.props.style ?? 'min-width: min-content;'"
<button type="button" class="btn" t-att-style="this.props.style"
t-att-data-action-id="info.actionId"
t-att-data-action-param="info.actionParam"
t-att-data-action-value="info.actionValue"
Expand Down
157 changes: 157 additions & 0 deletions addons/html_builder/static/src/core/building_blocks/builder_list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { BuilderComponent } from "@html_builder/core/building_blocks/builder_component";
import {
basicContainerBuilderComponentProps,
useBuilderComponent,
useInputBuilderComponent,
} from "@html_builder/core/utils";
import { Component, useRef } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation";
import { useSortable } from "@web/core/utils/sortable_owl";

export class BuilderList extends Component {
static template = "html_builder.BuilderList";
static props = {
...basicContainerBuilderComponentProps,
id: { type: String, optional: true },
addItemTitle: { type: String, optional: true },
itemShape: {
type: Object,
values: [{ value: "number" }, { value: "text" }],
validate: (value) =>
// is not empty object and doesn't include reserved fields
Object.keys(value).length > 0 && !Object.keys(value).includes("_id"),
optional: true,
},
default: { optional: true },
sortable: { optional: true },
hiddenProperties: { type: Array, optional: true },
};
static defaultProps = {
addItemTitle: _t("Add"),
itemShape: { value: "text" },
default: { value: _t("Item") },
sortable: true,
hiddenProperties: [],
};
static components = { BuilderComponent };

setup() {
this.validateProps();

useBuilderComponent();
const { state, commit, preview } = useInputBuilderComponent({
id: this.props.id,
defaultValue: this.parseDisplayValue([this.makeDefaultItem()]),
parseDisplayValue: this.parseDisplayValue,
formatRawValue: this.formatRawValue,
});
this.state = state;
this.commit = commit;
this.preview = preview;

if (this.props.sortable) {
useSortable({
enable: () => this.props.sortable,
ref: useRef("table"),
elements: ".o_row_draggable",
handle: ".o_handle_cell",
cursor: "grabbing",
placeholderClasses: ["d-table-row"],
onDrop: (params) => {
const { element, previous } = params;
this.reorderItem(element.dataset.id, previous?.dataset.id);
},
});
}
}

validateProps() {
// keys match
const itemShapeKeys = Object.keys(this.props.itemShape);
const defaultKeys = Object.keys(this.props.default);
const allKeys = new Set([...itemShapeKeys, ...defaultKeys]);
if (allKeys.size !== itemShapeKeys.length) {
throw new Error("default properties don't match itemShape");
}
}

parseDisplayValue(displayValue) {
return JSON.stringify(displayValue);
}

formatRawValue(rawValue) {
const items = rawValue ? JSON.parse(rawValue) : [];
for (const item of items) {
if (!("_id" in item)) {
item._id = this.getNextAvailableItemId(items);
}
}
return items;
}

addItem() {
const items = this.formatRawValue(this.state.value);
items.push(this.makeDefaultItem());
this.commit(items);
}

deleteItem(e) {
const itemId = e.target.dataset.id;
const items = this.formatRawValue(this.state.value);
this.commit(items.filter((item) => item._id !== itemId));
}

reorderItem(itemId, previousId) {
let items = this.formatRawValue(this.state.value);
const itemToReorder = items.find((item) => item._id === itemId);
items = items.filter((item) => item._id !== itemId);

const previousItem = items.find((item) => item._id === previousId);
const previousItems = items.slice(0, items.indexOf(previousItem) + 1);

const nextItems = items.slice(items.indexOf(previousItem) + 1, items.length);

const newItems = [...previousItems, itemToReorder, ...nextItems];
this.commit(newItems);
}

makeDefaultItem() {
return {
...this.props.default,
_id: this.getNextAvailableItemId(),
};
}

getNextAvailableItemId(items) {
items = items || this.formatRawValue(this.state?.value);
const biggestId = items
.map((item) => parseInt(item._id))
.reduce((acc, id) => (id > acc ? id : acc), -1);
const nextAvailableId = biggestId + 1;
return nextAvailableId.toString();
}

onInput(e) {
this.handleValueChange(e.target, false);
}

onChange(e) {
this.handleValueChange(e.target, true);
}

handleValueChange(targetInputEl, commitToHistory) {
const id = targetInputEl.dataset.id;
const propertyName = targetInputEl.name;
const value = targetInputEl.value;

const items = this.formatRawValue(this.state.value);
const item = items.find((item) => item._id === id);
item[propertyName] = value;

if (commitToHistory) {
this.commit(items);
} else {
this.preview(items);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<template xml:space="preserve">

<t t-name="html_builder.BuilderList">
<BuilderComponent>
<t t-if="state.value?.length > 2">
<div class="o_we_table_wrapper">
<table t-ref="table">
<t t-foreach="formatRawValue(state.value)" t-as="item" t-key="item._id">
<tr class="o_row_draggable" t-att-data-id="item._id">
<td t-if="props.sortable" class="o_handle_cell">
<button type="button" class="btn fa fa-fw fa-arrows"/>
</td>
<t t-foreach="Object.entries(props.itemShape).filter(([key,_]) => !props.hiddenProperties.includes(key))" t-as="entry" t-key="entry[0]">
<td>
<input
t-att-type="entry[1]"
t-att-name="entry[0]"
t-att-value="item[entry[0]]"
t-att-data-id="item._id"
t-on-input="onInput"
t-on-change="onChange"
/>
</td>
</t>
<td>
<button type="button" class="btn o_we_text_danger builder_list_remove_item fa fa-fw fa-minus"
t-on-click="deleteItem"
t-att-data-id="item._id"/>
</td>
</tr>
</t>
</table>
</div>
</t>
<button type="button" class="btn builder_list_add_item"
t-on-click="addItem"><t t-out="props.addItemTitle"/></button>
</BuilderComponent>
</t>

</template>
12 changes: 12 additions & 0 deletions addons/html_builder/static/src/sidebar/option_container.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
font-size: 12px;
padding: 1px 6px;
border: none;
min-width: min-content;

&.active {
color: $o-we-fg-lighter;
Expand All @@ -14,6 +15,10 @@
&>img {
padding-bottom: 2px;
}

&.o_we_text_danger {
color: $o-we-color-danger;
}
}

.btn-primary {
Expand Down Expand Up @@ -47,6 +52,7 @@
width: 2em;
}

@include o-input-number-no-arrows();
input {
border: $o-we-sidebar-content-field-border-width solid $o-we-sidebar-content-field-border-color;
border-radius: $o-we-sidebar-content-field-border-radius;
Expand All @@ -58,6 +64,12 @@
border-color: $o-we-sidebar-content-field-input-border-color;
}
}

.o_we_table_wrapper {
width: 100%;
max-height: 200px;
overflow-y: auto;
}
}

.o_we_img_animate:hover img {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,9 @@ html[lang] > body.editor_enable [data-oe-translation-state] {
pointer-events: none;
}
}

html[data-edit_translations="1"] {
.o_translate_mode_hidden {
display: none !important;
}
}
104 changes: 104 additions & 0 deletions addons/html_builder/static/src/website_payment/donation_option.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">

<t t-name="website_payment.DonationOption">
<BuilderRow label.translate="Recipient Email">
<BuilderTextInput dataAttributeAction="'donationEmail'" />
</BuilderRow>
<BuilderRow label.translate="Display Options" preview="false">
<BuilderCheckbox id="'display_options_opt'" action="'toggleDisplayOptions'" />
</BuilderRow>
<BuilderRow t-if="!isActiveItem('no_input_opt')"
label.translate="Pre-filled Options" preview="false">
<BuilderCheckbox id="'pre_filled_opt'" action="'togglePrefilledOptions'" />
</BuilderRow>
<t t-if="isActiveItem('no_input_opt') || isActiveItem('pre_filled_opt')">
<t t-set="translatedDefaultDescription">Add a description here</t>
<BuilderList
action="'setPrefilledOptions'"
addItemTitle.translate="Add new pre-filled option"
itemShape="{ value: 'number', description: 'text' }"
default="{ value: '50', description: translatedDefaultDescription }"
hiddenProperties="isActiveItem('pre_filled_descriptions_opt') ? [] : ['description']"
/>
<BuilderRow label.translate="Descriptions" level="1" preview="false">
<BuilderCheckbox id="'pre_filled_descriptions_opt'" action="'toggleDescriptions'" />
</BuilderRow>
</t>
<BuilderRow label.translate="Custom Amount" preview="false">
<BuilderSelect action="'selectAmountInput'">
<t t-if="!isActiveItem('display_options_opt') || isActiveItem('pre_filled_opt') || isActiveItem('no_input_opt')">
<BuilderSelectItem id="'free_amount_opt'" actionParam="'freeAmount'">Input</BuilderSelectItem>
</t>
<t t-if="isActiveItem('display_options_opt')">
<BuilderSelectItem id="'slider_opt'" actionParam="'slider'">Slider</BuilderSelectItem>
</t>
<t t-if="isActiveItem('no_input_opt') || isActiveItem('pre_filled_opt')">
<BuilderSelectItem id="'no_input_opt'" actionParam="''">None</BuilderSelectItem>
</t>
</BuilderSelect>
</BuilderRow>
<BuilderRow t-if="!isActiveItem('no_input_opt')"
label.translate="Minimum" level="1">
<BuilderNumberInput step="1" action="'setMinimumAmount'"/>
</BuilderRow>
<t t-if="isActiveItem('slider_opt')">
<BuilderRow label.translate="Maximum" level="1">
<BuilderNumberInput step="1" action="'setMaximumAmount'"/>
</BuilderRow>
<BuilderRow label.translate="Step" level="1">
<BuilderNumberInput step="1" action="'setSliderStep'"/>
</BuilderRow>
</t>
<BuilderRow label.translate="Default Amount">
<BuilderNumberInput step="1" default="25" dataAttributeAction="'defaultAmount'"/>
</BuilderRow>
</t>

<t t-name="website_payment.donation.descriptionTranslationInputs">
<t t-foreach="descriptions" t-as="description" t-key="description_index">
<input type="hidden" class="o_translatable_input_hidden d-block mb-1 w-100" name="donation_descriptions" t-att-value="description"/>
</t>
</t>

<t t-name="website_payment.donation.prefilledButtons">
<div class="s_donation_prefilled_buttons mb-2">
<t t-foreach="prefilled_buttons" t-as="prefilled_button_value" t-key="prefilled_button_value_index">
<button class="s_donation_btn btn btn-outline-primary btn-lg mb-2 me-1 o_not_editable"
type="button"
contenteditable="false"
t-att-data-donation-value="prefilled_button_value"
t-esc="prefilled_button_value"/>
</t>
<span t-if="custom_input" class="s_donation_btn s_donation_custom_btn btn btn-outline-primary btn-lg mb-2 me-1">
<input id="s_donation_amount_input" type="number" t-att-min="minimum_amount" class="" placeholder="Custom Amount" aria-label="Amount"/>
</span>
</div>
</t>
<t t-name="website_payment.donation.prefilledButtonsDescriptions">
<div class="s_donation_prefilled_buttons my-4">
<t t-foreach="prefilled_buttons" t-as="prefilled_button" t-key="prefilled_button_value_index">
<div class="s_donation_btn_description d-sm-flex align-items-center my-3 o_not_editable o_translate_mode_hidden" contenteditable="false">
<button class="s_donation_btn btn btn-outline-primary btn-lg me-3"
type="button"
t-att-data-donation-value="prefilled_button.value"
t-esc="prefilled_button.value"/>
<p class="s_donation_description mt-2 my-sm-auto text-muted fst-italic" t-esc="prefilled_button.description"></p>
</div>
</t>
<div t-if="custom_input" class="d-sm-flex align-items-center my-3">
<span class="s_donation_btn s_donation_custom_btn btn btn-outline-primary btn-lg">
<input id="s_donation_amount_input" type="number" t-att-min="minimum_amount" placeholder="Custom Amount" aria-label="Amount"/>
</span>
</div>
</div>
</t>
<t t-name="website_payment.donation.slider">
<div class="s_donation_range_slider_wrap mb-2 position-relative">
<label for="s_donation_range_slider">Choose Your Amount</label>
<input type="range" class="form-range" t-att-min="minimum_amount" t-att-max="maximum_amount" t-att-step="slider_step" id="s_donation_range_slider" contenteditable="false"/>
<output class="s_range_bubble" contenteditable="false">25</output>
</div>
</t>

</templates>
Loading