Skip to content
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

Update admin cart page dynamically #1715

Merged
merged 27 commits into from
Mar 2, 2017

Conversation

jhawthorn
Copy link
Contributor

@jhawthorn jhawthorn commented Feb 16, 2017

Built on top of #1710

This fully models the displayed information on the cart page in backbone, which allows it to update dynamically without relying on the previous window.location.reload().

This allows for smoother interaction by the user, and avoids undesirable changes from the refresh like closing in-progress edits.

@@ -0,0 +1,10 @@
(function() {
var defaultLocale = document.documentElement.lang;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a default fallback language for when locale is not set?

I'd assume that would be English. In that case, this makes more sense:

var defaultLocale = document.documentElement.lang || 'en';

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will always be set. I don't think we need to be defensive here.

Spree.formatMoney = function(amount, currency, options) {
options = (options || {});
var locale = options.locale || defaultLocale;
var format = new Intl.NumberFormat(locale, {style: 'currency', currency: currency})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a big discussion whether semicolons should be always added or not in JS. I'd lean towards the "mandatory semicolons", simply because it prevents unexpected errors from happening. You can see one such example in this SO answer:
http://stackoverflow.com/a/8108912/336806

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Speaking of, is there any linter used for JS? What are the JS style rules (e.g. semicolons)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope. Please feel free to file an issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed

(function() {
var defaultLocale = document.documentElement.lang;

Spree.formatMoney = function(amount, currency, options) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function looks like something I'd like to be able to also use on frontend. It seems to be impossible without including solidus_backend. Is there a central place where such functions could go?

Also, defaultLocale could be calculated in a centralized place.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can appreciate that, but I'm not 100% sure that this will be the permanent implementation of this method. We might have to end up including accounting.js or similar, which we would not want cluttering up the frontend. For that reason, I want to keep this backend-only.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we can both agree that "temporary fix" tends to stay around much longer than what we planned it to stay. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a temporary fix. The frontend and backend have different javascript, different requirements, and different browser support.

@@ -124,6 +124,11 @@ button, .button {
width: 100%;
text-align: center;
}

&[disabled], &.disabled {
Copy link
Contributor

@vfonic vfonic Feb 17, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the more preferred way would be to use :disabled CSS3 selector. Although they are the same, some explanation why use one over the [disabled] can be found here:
http://stackoverflow.com/a/20146936/336806

It works in cross-browser:
http://caniuse.com/#feat=css-sel3

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call 👍

<thead>
<tr>
<th><%= Spree::Adjustment.human_attribute_name(:name)%></th>
<th><%= Spree::Adjustment.human_attribute_name(:amount)%></th>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be translated:
Screenshot:
Screenshot

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, this is a solidus_i18n issue

<% adjustments.eligible.group_by(&:label).each do |label, adjustments| %>
<tr class="total">
<td><strong><%= label %>:</strong></td>
<td class="total align-center"><span><%= Spree::Money.new(adjustments.sum(&:amount), currency: adjustments.first.order.try(:currency)) %></span></td>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to these changes, but you could add this fix in:

currency: adjustments.first.order.try(:currency)

=>

currency: order.currency

...since you're always passing order variable and it's always defined.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. We should address this in this PR

sku = order.line_items.first.variant.sku
expect(page).to have_content("SKU: #{sku}")
context "with a completed order" do
let!(:order) { create(:completed_order_with_totals) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better having this inlined inside of the it block:

order = create(:completed_order_with_totals)

It's always better to have the code contained in the single place, than trying to figure out where did this variable come from (especially if let! is used and not let).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using let is better for composability and is always preferred. This let actually overrides a let!(:order) higher up in the file, which the local definition would not do. This is a good thing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean it's always preferred in solidus project(s) or it's generally always preferred? Because I prefer not to use it and I can find some good arguments why it should be avoided:
https://robots.thoughtbot.com/lets-not
http://linduxed.com/blog/2014/08/24/writing-more-readable-rspec-tests/

Of course there are always arguments pro and contra. Just asking out of curiosity.

@@ -711,6 +711,7 @@ en:
add_action_of_type: Add action of type
add_country: Add Country
add_coupon_code: Add Coupon Code
add_line_item: Add Item
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any process for reporting this new translation to solidus_i18n? Perhaps an issue could be created on the solidus_i18n repo when translation-changes/new-translation-is-added?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a rake task in solidus_i18n for this purpose. This question is way outside the scope of this PR.

Spree.formatMoney = function(amount, currency, options) {
options = (options || {});
var locale = options.locale || defaultLocale;
var format = new Intl.NumberFormat(locale, {style: 'currency', currency: currency})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed

(function() {
var defaultLocale = document.documentElement.lang;

Spree.formatMoney = function(amount, currency, options) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we can both agree that "temporary fix" tends to stay around much longer than what we planned it to stay. :)

sku = order.line_items.first.variant.sku
expect(page).to have_content("SKU: #{sku}")
context "with a completed order" do
let!(:order) { create(:completed_order_with_totals) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean it's always preferred in solidus project(s) or it's generally always preferred? Because I prefer not to use it and I can find some good arguments why it should be avoided:
https://robots.thoughtbot.com/lets-not
http://linduxed.com/blog/2014/08/24/writing-more-readable-rspec-tests/

Of course there are always arguments pro and contra. Just asking out of curiosity.

Copy link
Member

@tvdeyen tvdeyen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A great UX improvement. 👍

I have some little code styling nits and I'm not sure if we should put all these backbone models and views into large files or if it would be better to have them separate overridable files instead.

As always 🍻 work!

<% adjustments.eligible.group_by(&:label).each do |label, adjustments| %>
<tr class="total">
<td><strong><%= label %>:</strong></td>
<td class="total align-center"><span><%= Spree::Money.new(adjustments.sum(&:amount), currency: adjustments.first.order.try(:currency)) %></span></td>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. We should address this in this PR

});
model.fetch(options);
return model;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should split this file into files for each model? So devs could override individual models (the order for instance)? Not sure though, if this could ever be the case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. Going to split these into separate files and also move to a different namespace, Spree.Models.Order, which will also make more sense as we add more models (like Spree.Models.Variant)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏻 for the namespace


render: ->
@$el.prop("disabled", !this.collection.length || this.collection.some( (item) -> item.isNew() ))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kinda confuses me, that some files are coffeescript and some are javascript. I would prefer to go with JS from now on. But we should be consistent.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also maybe splitting these views into separate overridable files would be preferable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also prefer JS, but this file was originally coffeescript and the changes were each incremental

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Fair enough.

this.$el.toggleClass('hidden', !lineItemCount)
this.$('.order-total').text(this.model.get("display_total"))
}
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also consider to split this file up

this.$('dd.order-shipment_state').html(this.renderState('shipment_state', this.model.get("shipment_state")))

this.$('.order-payment_state').toggleClass("hidden", !this.model.get("completed_at"))
this.$('dd.order-payment_state').html(this.renderState('payment_state', this.model.get("payment_state")))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a backbone expert, but do we really need to use this.$ over just $?

Copy link
Contributor Author

@jhawthorn jhawthorn Feb 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. this.$ is scoped within the view's element. It's great!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL. Indeed very nice 👌🏻

<a class="edit-line-item fa fa-edit no-text with-tip" href="#" title="{{ t "actions.edit" }}"></a>
<a class="delete-line-item fa fa-trash no-text with-tip" href="#" title="{{ t "actions.delete" }}"></a>
{{/if}}
</td>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the indentation in this file messed up somehow.

}

.select2-container.error {
border-radius: 4px;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we have a global $border-radius variable we can use here.

:title => Spree.t(:order_adjustments)
} %>
<%= render :partial => "spree/admin/orders/carton", :collection => @order.cartons.order(:shipped_at), :locals => { :order => order } %>
<%= render :partial => "spree/admin/orders/shipment", :collection => @order.shipments.order(:created_at), :locals => { :order => order } %>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should only use Ruby 1.9 Hash syntax if we touch lines of code

<dt><%= Spree::Shipment.model_name.human %>: </dt>
<dd id='shipment_status'><span class="state <%= @order.shipment_state %>"><%= Spree.t(@order.shipment_state, :scope => :shipment_states, :default => [:missing, "none"]) %></span></dd>
<dt><%= Spree::Payment.model_name.human %>: </dt>
<dd id='payment_status'><span class="state <%= @order.payment_state %>"><%= Spree.t(@order.payment_state, :scope => :payment_states, :default => [:missing, "none"]) %></span></dd>
<dt data-hook='admin_order_tab_date_completed_title'><%= Spree::Order.human_attribute_name(:completed_at) %>:</dt>
<dd id='date_complete'><%= pretty_time(@order.completed_at) %></dd>
<% end %>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This view could profit from some indenting and usage of Ruby 1.9 hash syntax.

@tvdeyen tvdeyen mentioned this pull request Feb 28, 2017
@jhawthorn jhawthorn force-pushed the cart_page_ajax_updates branch from 6610fe4 to e7cc666 Compare March 1, 2017 22:12
@jhawthorn
Copy link
Contributor Author

@tvdeyen This is ready for review :)

Models and views are now namespaced and in the models/ and views/ directories. I believe I have addressed the other feedback.

Tests have passed, but CircleCI is being CircleCI and hasn't updated github.

Copy link
Member

@tvdeyen tvdeyen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 I really love the way this goes. Very well structured backend code. This is a huge improvement for us. Thanks for working on this, John! 🍬 🥃 🎂

Just one thought. Could we get rid off the need to create the Spree.Models and Spree.Views objects in each file by adding spree/backend/models/base and spree/backend/views/base files and require these instead? Nothing we need to tackle now.

This is ready to be merged IMO 👍

@@ -0,0 +1,9 @@
//= require spree/backend/routes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need routes in this file

@tvdeyen
Copy link
Member

tvdeyen commented Mar 2, 2017

@jhawthorn I see a fixup commit. Maybe you need to rebase once more?

Works around an issue where an Order was being fetched by the server at
the same time the spec was creating an order by factory.

   SQLite3::BusyException: database is locked:
This simplifies fetching the order from the API, which we will want to
do in several places. It hides the implementation detail of the
placeholder arrays for the line_items and shipments collections.
@jhawthorn jhawthorn force-pushed the cart_page_ajax_updates branch from e7cc666 to 4ea9fb9 Compare March 2, 2017 19:19
@jhawthorn jhawthorn merged commit 663aee0 into solidusio:master Mar 2, 2017
@tvdeyen
Copy link
Member

tvdeyen commented Mar 2, 2017

🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants