Ember View created with performance in mind, instead of using any template library, the DOM code is injected directly from the javascript.
Loading this screen of the app takes aprox 100ms even on slowest computers/mobiles.
ModifiersPopup = Ember.View.extend | |
tagName: "div" | |
templateName: "menus/modifiers_popup" | |
product: null | |
related_categories: [] | |
unrelated_categories: [] | |
unrelated_cats_options: | |
per_page: 11 | |
viewport: [0, 11] | |
selected_modifiers: [] | |
selected_category: null | |
mode: "add" | |
### HTML Templates ### | |
htmlOfCategories: (categories) -> | |
html = "" | |
categories.forEach (category) => html = html + @htmlOfCategory(category) | |
return html | |
htmlOfCategory: (category) -> | |
category_id = parseInt(category.get('id')) | |
html = "" | |
return html if category.get("modifiers").get("length") == 0 | |
html = html + | |
""" | |
<div class='modifier-category' data-category-id=#{category.get('id')}> | |
<div class='header'> | |
<div class='title left'> #{category.get('name')} </div> | |
<div class='requirements left'> | |
#{@htmlOfModifierCategoryRequirements category_id} | |
</div> | |
</div> | |
<div class='items'> | |
""" | |
category.get("modifiers_not_deleted").forEach (modifier) => | |
html = html + @htmlOfModifier(modifier) | |
html = html + | |
""" | |
</div> | |
</div> | |
""" | |
return html | |
htmlOfModifier: (modifier) -> | |
html = "" | |
html = html + | |
""" | |
<a class='modifier-button' data-modifier-id=#{modifier.get('id')}> | |
<div class='wrapper'> | |
<div class='text'> | |
<div class='title'> #{modifier.get('visible_name')} </div> | |
""" | |
unless modifier.get("price") == 0 | |
html = html + | |
""" | |
<div class='price'> #{currency modifier.get('price')} </div> | |
""" | |
html = html + | |
""" | |
</div> | |
</div> | |
</a> | |
""" | |
return html | |
htmlOfFollowOnModifierCategory: (follow_on_modifier_cat, parent_modifier) -> | |
html = "" | |
html = html + | |
""" | |
<div class='follow-on-modifier-category' | |
data-category-id=#{follow_on_modifier_cat.get('id')}> | |
<div class='header'> | |
<div class='title left'> | |
#{parent_modifier.get('visible_name')} | |
requires | |
#{follow_on_modifier_cat.get('name')} | |
</div> | |
</div> | |
<div class='items'> | |
""" | |
follow_on_modifier_cat.get("modifiers").forEach (modifier) => | |
html = html + @htmlOfModifier(modifier) | |
html = html + | |
""" | |
</div> | |
</div> | |
""" | |
return html | |
htmlOfUnrelatedCategories: (viewport) -> | |
html = "" | |
html = html + | |
""" | |
<a class='unrelated-cat-btn active' | |
data-action='preset' | |
data-unrelated-category-id='preset'> | |
<div class='wrapper'> | |
<div class='text'> | |
Preset | |
</div> | |
</div> | |
</a> | |
""" | |
@get("unrelated_categories") | |
.slice(viewport[0], viewport[1]) | |
.forEach (category) => | |
html = html + | |
""" | |
<a class='unrelated-cat-btn' | |
data-unrelated-category-id=#{category.get('id')} > | |
<div class='wrapper'> | |
<div class='text'> | |
#{category.get('name')} | |
</div> | |
</div> | |
</a> | |
""" | |
return html | |
htmlOfModifierCategoryRequirements: (category_id) -> | |
max = @get("product").maximumModifierChoices(category_id) | |
min = @get("product").minimumModifierChoices(category_id) | |
html = "" | |
html = html + " Maximum of " + max unless max is 0 or !max? | |
html = html + " Minimum of " + min unless min is 0 or !min? | |
return html | |
### Handlers ### | |
willInsertElement: -> | |
@set "mode", "add" | |
@set "unrelated_cats_options.viewport", [0, 11] | |
@set "selected_category", "preset" | |
@set "selected_modifiers", [] | |
@set "product", null | |
didInsertElement: -> | |
@set "product", @get("controller.active_modifiers.product") | |
@set "related_categories", | |
@get("product").modifierCategories() | |
@set "unrelated_categories", | |
@get("product").unrelatedModifierCategories @get("related_categories") | |
@prepareTemplate() | |
# Load previously selected modifiers if product is not new in the till roll | |
@loadSelectedModifiers() unless @get("controller.active_modifiers.new_product") | |
touchStart: (event) -> | |
@performAction(event) if window.isTouchDevice | |
mouseUp: (event) -> | |
@performAction(event) if !window.isTouchDevice | |
performAction: (event) -> | |
el = $(event.target).closest("a") | |
# Back / Done | |
@backButtonPressed() if el.data("action") is "back" | |
@doneButtonPressed() if el.data("action") is "save" | |
# Add Note | |
@addNoteButtonPressed() if el.data("action") is "add-note" | |
# Mode | |
@addModeButtonPressed(el) if el.data("action") is "add" | |
@dontAddModeButtonPressed(el) if el.data("action") is "dont-add" | |
# Category related | |
@presetButtonPressed() if el.data("action") is "preset" | |
@modifierButtonPressed(el) if el.data("modifier-id") | |
@categoryButtonPressed(id) if id = el.data("unrelated-category-id") | |
# Category list | |
@previousPageButtonPressed(el) if el.data("action") is "previous-page" | |
@nextPageButtonPressed(el) if el.data("action") is "next-page" | |
backButtonPressed: -> @close() | |
doneButtonPressed: -> @save() | |
addNoteButtonPressed: -> | |
@save() | |
@get("controller.controllers.more_menu").addNoteButtonPressed() | |
addModeButtonPressed: (el) -> | |
unless @get("mode") == "add" | |
$(".add-btn").toggleClass "selected" | |
$(".dont-add-btn").toggleClass "selected" | |
@set "mode", "add" | |
dontAddModeButtonPressed: (el) -> | |
unless @get("mode") == "dont_add" | |
$(".add-btn").toggleClass "selected" | |
$(".dont-add-btn").toggleClass "selected" | |
@set "mode", "dont_add" | |
modifierButtonPressed: (el) -> | |
status = el.data("status") | |
modifier = DS.Modifier.find(el.data("modifier-id")) | |
category = modifier.get("modifier_category") | |
max_reached = @aboveMaxModifiersInCategory(category) | |
# This is not working !!!!! | |
return if @get("mode") == "add" and max_reached and status != @get("mode") | |
switch status | |
when undefined | |
@selectModifier modifier, @get("mode") | |
when @get("mode") | |
@unselectModifier modifier | |
else | |
@changeModifier modifier, @switchStatus(status) | |
presetButtonPressed: -> | |
@categoryButtonPressed "preset" | |
categoryButtonPressed: (category_id) -> | |
if @get("selected_category") == "preset" | |
previous = "preset" | |
else | |
previous = @get("selected_category.id") | |
$("[data-unrelated-category-id=#{previous}]") | |
.removeClass "active" | |
$("[data-unrelated-category-id=#{category_id}]") | |
.addClass "active" | |
@selectCategory category_id | |
@showSelectedModifiers() | |
previousPageButtonPressed: (el) -> | |
viewport = @get "unrelated_cats_options.viewport" | |
per_page = @get "unrelated_cats_options.per_page" | |
if viewport[0] > 0 | |
viewport = [viewport[0] - per_page, viewport[0]] | |
document | |
.getElementById("unrelated-categories-list-html") | |
.innerHTML = @htmlOfUnrelatedCategories(viewport) | |
@set "unrelated_cats_options.viewport", viewport | |
nextPageButtonPressed: (el) -> | |
viewport = @get "unrelated_cats_options.viewport" | |
per_page = @get "unrelated_cats_options.per_page" | |
if viewport[1] < @get("unrelated_categories.length") | |
viewport = [viewport[1], viewport[1] + per_page] | |
document | |
.getElementById("unrelated-categories-list-html") | |
.innerHTML = @htmlOfUnrelatedCategories(viewport) | |
@set "unrelated_cats_options.viewport", viewport | |
### Logic ### | |
prepareTemplate: -> | |
document | |
.getElementById("title") | |
.innerHTML = "Modify #{@get('product.name')}" | |
document | |
.getElementById("modifiers-list-html") | |
.innerHTML = @htmlOfCategories(@get("related_categories")) | |
document | |
.getElementById("unrelated-categories-list-html") | |
.innerHTML = @htmlOfUnrelatedCategories(@get("unrelated_cats_options.viewport")) | |
# Show unrelated categories controls if list is long | |
max_viewport = this.get("unrelated_cats_options.viewport.lastObject") | |
if @get("unrelated_categories.length") > max_viewport | |
$("a.prev-next-button .wrapper").show() | |
loadSelectedModifiers: -> | |
line_item = @get("controller.controllers.order.selectedLineItem") | |
return unless line_item? | |
@set "selected_modifiers", [] | |
line_item.get("line_item_modifiers").toArray().forEach (lim) => | |
@selectModifier lim.get("modifier"), lim.get("status") | |
selectModifier: (modifier, status) -> | |
follow_on_modifier_cat = modifier.followOnModifierCategory() | |
id = modifier.get("id") | |
$("[data-modifier-id=#{id}]") | |
.data("status", status) | |
.attr("data-status", status) | |
modifiers = @get("selected_modifiers") | |
modifiers.push {id: id, status: status} | |
@set "selected_modifiers", modifiers | |
if status == "add" and follow_on_modifier_cat? | |
@showFollowOnModifierCategory follow_on_modifier_cat, modifier | |
unselectModifier: (modifier) -> | |
follow_on_modifier_cat = modifier.followOnModifierCategory() | |
id = modifier.get("id") | |
$("[data-modifier-id=#{id}]") | |
.removeData("status") | |
.removeAttr("data-status") | |
modifiers = @get("selected_modifiers").filter (m) -> | |
m.id != id | |
@set "selected_modifiers", modifiers | |
if follow_on_modifier_cat? | |
@hideFollowOnModifierCategory follow_on_modifier_cat | |
changeModifier: (modifier, status) -> | |
follow_on_modifier_cat = modifier.followOnModifierCategory() | |
id = modifier.get("id") | |
$("[data-modifier-id=#{id}]") | |
.data("status", status) | |
.attr("data-status", status) | |
@get("selected_modifiers").forEach (m) -> | |
m.status = status if m.id == id | |
if follow_on_modifier_cat? | |
if status == "add" | |
@showFollowOnModifierCategory follow_on_modifier_cat, modifier | |
else | |
@hideFollowOnModifierCategory follow_on_modifier_cat | |
switchStatus: (status) -> | |
if status == "add" | |
return "dont_add" | |
else | |
return "add" | |
selectCategory: (category_id) -> | |
if category_id == "preset" | |
@set "selected_category", "preset" | |
html = @htmlOfCategories @get("related_categories") | |
else | |
@set "selected_category", DS.ModifierCategory.find(category_id) | |
html = @htmlOfCategory @get("selected_category") | |
document | |
.getElementById("modifiers-list-html") | |
.innerHTML = html | |
selectedModifiersInCategory: (category_id, type) -> | |
if type == "all" | |
status = "[data-status]" | |
else | |
status = "[data-status=#{type}]" | |
elements = $("[data-category-id=#{category_id}]") | |
.find(".modifier-button#{status}") | |
return $.map(elements , (i) -> | |
$(i).data("modifier-id") | |
) | |
# Returns true if the category is over the limit of allowed modifiers | |
aboveMaxModifiersInCategory: (category) -> | |
category_id = parseInt category.get("id") | |
max_in_cat = @get("product").maximumModifierChoices(category_id) | |
added_modifiers = @selectedModifiersInCategory(category_id, "add").length | |
return added_modifiers >= max_in_cat | |
# Returns true if the category is below the limit of required modifiers | |
belowMinModifiersInCategory: (category) -> | |
category_id = parseInt category.get("id") | |
min_in_cat = @get("product").minimumModifierChoices(category_id) | |
added_modifiers = @selectedModifiersInCategory(category_id, "add").length | |
return added_modifiers < min_in_cat | |
# Returns false if any related category does not have the min modifiers selected | |
modifierRequirementsNotFullfilled: -> | |
# We need to go to preset categories page since we use the DOM to navigate | |
# through the categories | |
@presetButtonPressed() | |
@get("related_categories").some (c) => @belowMinModifiersInCategory c | |
# Shows selected modifiers when changing category | |
showSelectedModifiers: -> | |
@get("selected_modifiers").forEach (m) => | |
$("[data-modifier-id=#{m.id}]") | |
.data("status", m.status) | |
.attr("data-status", m.status) | |
mod = DS.Modifier.find parseInt(m.id) | |
follow_on_modifier_cat = mod.followOnModifierCategory() | |
if m.status == "add" and follow_on_modifier_cat? | |
@showFollowOnModifierCategory follow_on_modifier_cat, mod | |
showFollowOnModifierCategory: (follow_on_modifier_cat, parent_modifier) -> | |
$("[data-modifier-id=#{parent_modifier.get('id')}]") | |
.parents("[data-category-id]") | |
.find(".items:first") | |
.append @htmlOfFollowOnModifierCategory(follow_on_modifier_cat, parent_modifier) | |
hideFollowOnModifierCategory: (follow_on_modifier_cat) -> | |
category_id = follow_on_modifier_cat.get("id") | |
follow_on_cat_modifiers = @selectedModifiersInCategory(category_id) | |
modifiers = @get("selected_modifiers").filter (i) -> | |
!follow_on_cat_modifiers.some (j) -> | |
parseInt(i.id) == j | |
@set "selected_modifiers", modifiers | |
$("[data-category-id=#{category_id}]").remove() | |
save: -> | |
line_item = @get("controller.controllers.order.selectedLineItem") | |
return if !line_item? | |
if @modifierRequirementsNotFullfilled() | |
subtleNiceError "The modifier minimum choices are not fulfilled for all the modifier categories." | |
return | |
# Delete current line_item_modifiers and add the new ones | |
line_item.get("line_item_modifiers").toArray().forEach (lim) => | |
lim.delete() | |
@get("selected_modifiers").forEach (m) -> | |
modifier = DS.Modifier.find m.id | |
if m.status == "dont_add" | |
price = 0 | |
else | |
price = modifier.get("price") | |
lim = DS.LineItemModifier.create | |
line_item: line_item | |
modifier: modifier | |
price: price | |
status: m.status | |
line_item.get("line_item_modifiers").pushObject lim | |
@close() | |
close: -> | |
@set "controller.active_modifiers", {} | |
@set "controller.show_modifiers_popup", false | |
scrollOrderToBottom() |