I Made A Tool For Setting Prices

Discussion in 'General Discussion' started by siquod, Feb 14, 2017.

Tags:
  1. siquod
    siquod Member
    Hello shapeways community,
    recently I opened a shop and I found that I had to set the end price for each material by hand. This is very tedious and it would have taken me days to do that for all my 85 products.
    So I decided to improve the usability of the pricing table. I wrote a little javascript that makes it possible to set the markup, both in dollar and in percent of the base price, for individual materials or several selected materials simultaneously. Also it lets you select all table rows that belong to a variant with one click, which is useful if you want to set the model file for a variant in all materials.
    You can download the script from my website.
    What do you think? What other pricing options should be available besides setting the markup in dollar and percent? How do you decide the prices of your models? Maybe I can build that in too.
    If you like my tool, you can thank me by helping to popularize my shop, Implicit Art.
    It would be cool if someone knowledgeable could make my script available in greasemonkey or so, because right now you have to paste it into the JS console every time. It would be even cooler if shapeways would heed my suggestion and make the pricing table more usable by default, so this hack is no longer necessary.
    Kind regards,
    Benjamin Berger

    Edit: I added a greasemonkey header to the script, thanks to stannum. Once installed, It now runs automatically.
     
    Last edited: Feb 15, 2017
    RC_Designs, GeorgeMiSelfie3D and IvoS like this.
  2. woody64
    woody64 Well-Known Member
    Didn't try it so far, but if it's doing what written, it provides:
    - either markup or percentage or total price
    - having header input field for these to generate it for several rows

    YES, that is what some including me are already asking for years(?) ....
     
    GeorgeMiSelfie3D likes this.
  3. stannum
    stannum Well-Known Member
    Putting in Greasemonkey is easy. Just add a new script from a product page, fill the fields (in includes use * to match, for namespace everyone seems to use a nick) and you are done. Like this:
    Code:
    // ==UserScript==
    // @name  SW Pricing Tool
    // @namespace  siquod
    // @description Helper for Shapeways pricing controls
    // @include  https://www.shapeways.com/product/edit/*
    // @version  1
    // @grant  none
    // ==/UserScript==
    
    function roundPrice(pr){
      if(String(Number(pr)).indexOf('.')==-1 || String(Number(pr)).endsWith('.0')){
      return String(Number(pr));
      }
      var cin = String(Number(pr)+0.005);
      var pr=cin.indexOf('.');
      if(pr== -1 || pr > cin.length-3)
      return pr;
      return cin.substr(0, pr+3);
    }
    
    function makeMarkupEditable(rowid){
      var row=$('[data-sw-material-pricing-row="'+rowid+'"]');
      if(row.find(".base-price-cell").get().length==0){
      return;
      }
    
      var percentInput=row.find(".percent-input");
      if(percentInput.length==0){
      percentInput.parent().remove();
      percentInput=$("<input></input>")
      .attr("type", "text")
      .addClass("amount-input percent-input")
      .css("width", "60px");
      var percentDiv=$("<div></div>")
      .addClass("sw-pricing-table__markup sw--margin-right-2 sw--margin-left-2 sw--float-left sw--text-right sw--font-size-12")
      .append(percentInput)
      .append("%");
      row.find(".markup-price-cell").after(percentDiv);
      percentInput.blur(function(){updateAfterPercentChange(rowid)});
      }
    
    
      var endPriceInput=row.find(".material-total-price");
      var markupSpan=row.find(".markup-price").children("[data-display-currency-component]");
      if(markupSpan.children().get().length>2){
      return;
      }
      markupSpan.children(":nth-child(2)").hide();
      var markupInput=$("<input></input>")
      .addClass("amount-input markup-input")
      .css("width", "60px")
      .css("text-align", "center");
      markupSpan.append(markupInput);
    
    
    
     
      markupInput.blur(function(){updateAfterMarkupChange(rowid)});
      updateAfterEndPriceChange(rowid);
    
    }
    function updateAfterMarkupChange(rowid){
      try{
      var row=$('[data-sw-material-pricing-row="'+rowid+'"]');
      var markupInput=row.find(".markup-input");
      var percentInput=row.find(".percent-input");
      if(markupInput.length==0) return;
      var basePriceSpan=row.find(".base-price-cell").find("[data-display-currency-component-value]");
      var endPriceInput=row.find(".material-total-price");
      var mu=Number(markupInput.val());
      var basePrice=Number(basePriceSpan.text());
      endPriceInput.val('$'+roundPrice(mu+basePrice));
      percentInput.val(roundPrice(mu*100/basePrice));
      endPriceInput.trigger("focusout");
      }catch(err){
      console.log(err);
      }
    };
    function updateAfterPercentChange(rowid){
      try{
      var row=$('[data-sw-material-pricing-row="'+rowid+'"]');
      var markupInput=row.find(".markup-input");
      var percentInput=row.find(".percent-input");
      if(markupInput.length==0) return;
      var basePriceSpan=row.find(".base-price-cell").find("[data-display-currency-component-value]");
      var basePrice=Number(basePriceSpan.text());
      var endPriceInput=row.find(".material-total-price");
      var mu=Number(percentInput.val())*basePrice*0.01;
      endPriceInput.val('$'+roundPrice(mu+basePrice));
      markupInput.val(roundPrice(mu));
      endPriceInput.trigger("focusout");
      }catch(err){
      console.log(err);
      }
    };
    function updateAfterEndPriceChange(rowid){
      try{
      var row=$('[data-sw-material-pricing-row="'+rowid+'"]');
      var basePriceSpan=row.find(".base-price-cell").find("[data-display-currency-component-value]");
      var endPriceInput=row.find(".material-total-price");
    
      var endPrice=endPriceInput.val();
      var basePrice=Number(basePriceSpan.text());
      endPrice=Number(endPrice.replace("$", ""));
      var markupInput=row.find(".markup-input");
      var percentInput=row.find(".percent-input");
      var mu=roundPrice(endPrice-basePrice);
      markupInput.val(mu);
      percentInput.val(roundPrice(mu*100.0/basePrice));
      }catch(err){
      console.log(err);
      }
    };
    function improveRow(rowid){
      var row=$('[data-sw-material-pricing-row="'+rowid+'"]');
      if(row.find(".base-price-cell").length==0){
      return;
      }
      var endPriceInput=row.find(".material-total-price");
      endPriceInput.not(".patched").addClass("patched").focusout(function(){
      setTimeout(function(){
      improveRow(rowid);
      }, 100);
      updateAfterEndPriceChange(rowid);
      });
      makeMarkupEditable(rowid);
    }
    
    function doToAllSelectedRows(fn){
      var rows=document.getElementsByClassName("sw-pricing-table__material-row");
      for(var i=0; i<rows.length; ++i) {
      var rowid = rows[i].getAttribute("data-sw-material-pricing-row");
      var cb=$(rows[i]).find("[data-sw-material-pricing-row-checkbox]");
      if(cb.is(':checked')){
      fn(rowid);
      }
      }
    }
    
    function improveStickyWrapper(){
    
      if($('[name="all-markup"]').length != 0)
      return;
      var loc = $(".sw-sticky-container").find("[data-sw-bulk-edit-for-sale]");
    
      var allMarkupInput=$("<input></input>") 
      .addClass("amount-input")
      .css("width", "60px")
      .css("text-align", "center");
      allMarkupInput.attr("name", "all-markup");
    
      var allPercentInput=$("<input></input>")
      .addClass("amount-input")
      .css("width", "60px")
      .css("text-align", "center");
      allPercentInput.attr("name", "all-percent");
    
      var allMarkup=$("<div></div>")
      .addClass("sw--vertical-center sw--float-left sw--padding-2-force")
      .append("$")
      .append(allMarkupInput);
    
      var allPercent=$("<div></div>")
      .addClass("sw--vertical-center sw--float-left sw--padding-2-force")
      .append(allPercentInput)
      .append("%");
     
      loc.before(allMarkup);
      loc.before(allPercent);
    
      var allEndPriceInput=$(".sw-sticky-container").find(".total-amount-cell");
    
      allEndPriceInput.focusout(function(){setTimeout(function(){doToAllSelectedRows(updateAfterEndPriceChange);}, 100)});
      allMarkupInput.focusout(function(){
      doToAllSelectedRows(function(rowid){
      if(allMarkupInput.val()=="") return;
      var row=$('[data-sw-material-pricing-row="'+rowid+'"]');
      var markupInput=row.find(".markup-input");
      if(markupInput.length==0) return;
      markupInput.val(roundPrice(allMarkupInput.val()));
      updateAfterMarkupChange(rowid);
      });
      });
      allPercentInput.focusout(function(){doToAllSelectedRows(function(rowid){
      if(allPercentInput.val()=="") return;
      var row=$('[data-sw-material-pricing-row="'+rowid+'"]');
      var percentInput=row.find(".percent-input");
      if(percentInput.length==0) return;
      percentInput.val(roundPrice(allPercentInput.val()));
      updateAfterPercentChange(rowid);
      });});
    }
    
    
    
    //Add inputs for applying absolute and percentual markup to all selected rows
    improveStickyWrapper();
    
    
    //Make the pricing table wider
    $(".container").css("width", "1120px");
    $("#sell-model-detail-container")
      .css("width", "1100px")
      .children(":nth-child(1)")
      .children(":nth-child(1)")
      .css("width", "1100px")
      .children(":nth-child(1)")
      .css("max-width", "1100px");
    
    
    //Add inputs to all rows to set absolute and percentual markup
    var rows=document.getElementsByClassName("sw-pricing-table__material-row");
    for(var i=0; i<rows.length; ++i)
      improveRow(rows[i].getAttribute("data-sw-material-pricing-row"));
    
    //Make it so that clicking a variant name toggles all checkboxes in that table section.
    $(".model-pricing-table")
      .find(".clearfix.sw--padding-top-2.sw--padding-left-7.sw--border-top-grey-1:not(.patched)")
      .click(function(){
      $(this)
      .nextUntil(".clearfix.sw--padding-top-2.sw--padding-left-7.sw--border-top-grey-1")
      .find("[data-sw-material-pricing-row-checkbox]")
      .trigger("click")
      })
      .css("cursor", "pointer")
      .addClass("patched");
    
    
    
    You may need to reload or load new product page for things to take effect. You can edit the script from GM menu.

    Edit: And thanks.
     
    Last edited: Feb 14, 2017
    GeorgeMiSelfie3D likes this.
  4. siquod
    siquod Member
    Ah, thank you, I had no idea greasemonkey was so easy. I just had heard about it, but never tried it.
    And yes, you have to save/reload the page every time you change something about the materials or variants because then the shapeways code of the page re-creates the pricing table. Any ideas how I can fix that? Maybe listen to the events that trigger the table re-creation, and re-run my code shortly afterwards?

    IMPORTANT: The copy of the script posted above by stannum is broken, because shapeways in the meantime has changed the class name of the table header from "sw-sticky-container" to "sw-sticky-container__element". I've updated the script on my website accordingly. I've also included the greasemonkey header. stannum, could you please edit your post so it shows the new version of the script, or remove your copy of the script so we have only one version of it to keep up to date?
     
  5. MrNibbles
    MrNibbles Well-Known Member
    How sweet it would be if straight or percentage mark-ups were integrated into the SW pricing pages. We should start a betting pool on if or when they finally implement it.
     
    RCRussell and GeorgeMiSelfie3D like this.
  6. siquod
    siquod Member
    UPDATE: The newest version of the script (on my website) does not require you to save and reload when adding a variant or material.
     
    cadycarlsondesigns likes this.
  7. woody64
    woody64 Well-Known Member
    Fine that was missing in my trial. Also was not aware that greasemonkey is that easy. I will definitly try that to get rid of the frustration when editing prices.

    Sorry @sw but this topic is ignored for years and as shown easy to solve.

    Maybe there are also other topics which can be solved that way, i.e.:
    - upload of images and correct resizing
    - adding a link to the product in mymodels. I know variants, but may solve it for 99% of the models
    - ...
     
  8. Foxworks
    Foxworks Member
    This is a great tool! I have just installed it in Greasemonkey and it runs without a hitch. Thanks a lot for developing this. It will surely save a lot of time with pricing future items!
     
  9. woody64
    woody64 Well-Known Member
    Yes it works., if installed it now Thanks for these two valuable inputs (script + Greasemonkey)
     
  10. MrNibbles
    MrNibbles Well-Known Member
    I will need to try this as well. I was about to not add any more variants or limit variants to one material and only add additional materials on request. Someone deserves an award for this!
     
  11. siquod
    siquod Member
    UPDATE: I found a very stupid bug in the roundPrices function that would sometimes set the end price to $2. It is fixed now. You can get the correct version from my website.
     
  12. ricswika
    ricswika Member
    Thank you. This script is fantastic.

    The only small glitch was I had to hit the return key in the script window to make the script run. This is probably obvious but got me at first. I am using it in Chrome on Windows 10. It works like a charm now.
     
  13. woody64
    woody64 Well-Known Member
    Last edited: Mar 5, 2017
  14. stannum
    stannum Well-Known Member
    You should use a different namespace.
     
  15. woody64
    woody64 Well-Known Member
    Done and script improved to:
    • load the page and extract the product link
    • if the page was loaded a console log for a future dictionary link is generated and permanently stored using the GM_setValue API call => this script is now selflearning the modelId => productId realtions
    • jump over product links (in my mymodel page there are some entries referencing to products only)
    drawbacks:
    • changing pages or setting filters seems to result in Ajax calls. I have added a callback after an Ajax call which does the update of the links in that cases. Seems to work, but not 100% sure ....

    Moved the topic to another discussion ...
     
    Last edited: Mar 5, 2017
  16. woody64
    woody64 Well-Known Member
    Deactivated the script for some days since I was switching from greasemonkey to tampermonkey and learned how valuable that script was for the last months.

    I would ask SW team to take a look on it and incorporate it as standard finishing the discussion if % or fixed markup or target price is the best choice.
    It simple depends ...

    Woody64
     
    GeorgeMiSelfie3D likes this.