title: JQuery Mobile with Google Apps Script
body: <p>I've made a Google Apps Script deployed as a standalone web app using HTMLService that provides a simple front end to enter budget data into a Google Spreadsheet. I'm using JQuery Mobile for some of the javascript as well as to style it a mobile-friendly manner, as my main use case for this app is to enter purchases from my mobile.</p>
  
  <p>My problem is that on a mobile browser, the app doesn't scale properly. It's the width of the browser, but it's as if it was "zoomed out". All the controls become essentially unusable on mobile.</p>
  
  <p>If the script is embedded in a Google Site, it scales properly, but I'd rather be able to view the web app directly, rather than embed it in Google Sites. </p>
  
  <p>EDIT: My rep is high enough to post photos now, so here they are (below code).</p>
  
  <p>EDIT: The beginning of my HTML is below. I originally had the javascript and the full HTML in here, and I can add snippets if needed, but I reviewed it again and don't think it's relelvant to the problem it was cluttering up the question, so I removed it.</p>
  
  <p><strong>HTML:</strong></p>
  
  <pre class="lang-html prettyprint-override"><code>&lt;!DOCTYPE html&gt;
  
  &lt;meta name="viewport" content="width=device-width, height=device-height, initial-scale=1"&gt;
  &lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"&gt;&lt;/script&gt;
  &lt;script src="https://ajax.googleapis.com/ajax/libs/jquerymobile/1.4.5/jquery.mobile.min.js"&gt;&lt;/script&gt;
  &lt;link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jquerymobile/1.4.5/jquery.mobile.min.css"&gt;
  &lt;?!= include('javascript'); ?&gt;
  
  &lt;div data-role="page" data-theme="a" id="main"&gt;
          &lt;div data-role="content"&gt;
              &lt;form id="myForm"&gt;
  ...
  </code></pre>
  
  <p><strong>Code.gs:</strong></p>
  
  <pre class="lang-js prettyprint-override"><code>function doGet() {
      return HtmlService.createTemplateFromFile('index').evaluate()
      .setSandboxMode(HtmlService.SandboxMode.IFRAME).setTitle('Budget Entry');
  }
  </code></pre>
  
  <p><img src="https://i.stack.imgur.com/FXhTW.png" alt="Direct Access (left) and Embedded in Google Sites (right)."></p>
  
  <p>Snippet with full code: </p>
  
  <p><div class="snippet" data-lang="js" data-hide="true" data-console="false" data-babel="false">
  <div class="snippet-code snippet-currently-hidden">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>//&lt;script&gt;  
    function formSuccess() {
      var dateSelect =    document.getElementById("date");
      var dateSelected =  dateSelect.options[dateSelect.selectedIndex].text;
      var catSelect =     document.getElementById("category");
      var catSelected =   catSelect.options[catSelect.selectedIndex].text;
      var amountEntered = document.getElementById("amount").value;
      var noteEntered =   document.getElementById("note").value;
  
      var successMsg = 'Date: ' + dateSelected + 
                  '&lt;br&gt;Category: ' + catSelected + 
                  '&lt;br&gt;Amount: $' + amountEntered + 
                  '&lt;br&gt;Note: ' + noteEntered;
                  
      $('#dialogMain').html(successMsg);
      $.mobile.silentScroll(0);
      $.mobile.changePage( "#dialog", { role: "dialog" } );
      
      requestCategoryInfo(document.getElementById("status"));
      document.getElementById("amount").value = '';
      document.getElementById("note").value = '';    
    }
    
    function submitForm() {
      if (document.getElementById('amount').value.length == 0) {
        alert('Please enter an amount.');
        return;
      }
      $.mobile.loading( 'show' );
      $('#status').html('');
      google.script.run
            .withSuccessHandler(formSuccess)
            .processForm(document.getElementById('myForm'));
    }
    
    function loadUI() {
      $.mobile.loading( 'show' );
      loadDateSelect();
      google.script.run.withSuccessHandler(loadCategoryNamesAndValues).withFailureHandler(sendLog)
        .getCategoryNamesAndValues();
      $.mobile.loading( 'hide' );
    }
    
    function loadDateSelect(){
      var d = new Date();
      var month = d.getMonth()+1;
      var today = d.getDate();
      var daysInAMonth = [0,31,28,31,30,31,30,31,31,30,31,30,31];
      
      for (var n=1; n &lt;= daysInAMonth[month]; n++) {
        var option = $("&lt;option&gt;").attr('value',n).text(month+"/"+n);
        $('#date').append(option);
      }
      $('#date').val(today);
      $('#date').selectmenu('refresh', true);
    }
    
    function loadCategoryNamesAndValues(catNamesAndValues){
      var namesAndValues = catNamesAndValues;
      var optionHTML = '';
      var currentGroup = '';
      var catName = '';
      var catID = '';
      
      for (var i=0; i&lt;namesAndValues.length; i++) {
        catName = namesAndValues[i][0];
        catID = namesAndValues[i][1];
        
        if (catID.toString() == "Group"){ // Handle Group Name
          
          if (currentGroup.length &gt; 0) { // close previous optgroup tag
             optionHTML += "&lt;/optGroup&gt;";
          } 
          
          // Open optGroup
          currentGroup = catName;
          optionHTML += "&lt;optGroup label='" + currentGroup + "'&gt;";
          
        } else if (isNaN(parseInt(catID)) || parseInt(catID) == 0){ //Do Nothing
        
        } else { // Create Option HTML as: &lt;option value=namesAndValues[i][1]&gt;namesAndValues[i][0]&lt;/option&gt;
        
          optionHTML += "&lt;option value='" + catID + "'&gt;" + catName + "&lt;/option&gt;";
        }
      }
      
      // Close current OptGroup
      optionHTML += "&lt;/optGroup&gt;"
      
      document.getElementById('category').innerHTML = optionHTML;
      $('#category').selectmenu('refresh', true);
    }
    
    function categoryChanged() {
      setStatus('');
      requestCategoryInfo(document.getElementById('status'));
    }
    
    function requestCategoryInfo(container) {
      $.mobile.loading( 'show' );
      google.script.run
            .withSuccessHandler(displayCategoryInfo)
            .withFailureHandler(sendLog)
            .withUserObject(container)
            .getCategoryInfo(document.getElementById('category').value);
    }
    
    function displayCategoryInfo(categoryInfo, container){
      var spentStr = 'Spent $' + categoryInfo.actual.toFixed(2) + ' of $' + categoryInfo.budgeted.toFixed(2);
      var remainingStr = 'Remaining: $' + categoryInfo.remaining.toFixed(2);
      
      var statusDiv = container;
      if (statusDiv.innerHTML.length &gt; 0){ statusDiv.innerHTML += '&lt;br&gt;'};
      statusDiv.innerHTML += spentStr + '&lt;br&gt;' + remainingStr;
      
      if (String(categoryInfo.fundAmount).length &gt; 0) {
        var fundAmountStr = '';
        
        if (categoryInfo.remaining &lt; 0) {
          fundAmountStr = (categoryInfo.fundAmount + categoryInfo.remaining).toFixed(2);
        } else {
          fundAmountStr = categoryInfo.fundAmount.toFixed(2);
        }
        
        statusDiv.innerHTML += '&lt;br&gt;Fund: $' + fundAmountStr;      
      }
      $.mobile.loading( 'hide' );
    }
    
    function setStatus(html){
      document.getElementById('status').innerHTML = html;
    }
    
    function appendStatus(html){
      setStatus(document.getElementById('status').innerHTML + '&lt;br&gt;' + html);
    }
    
    function sendLog(){
      google.script.run.sendLog();
    }
  //&lt;/script&gt;</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;!DOCTYPE html&gt;
  
  &lt;meta name="viewport" content="width=device-width, height=device-height, initial-scale=1"&gt;
  &lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"&gt;&lt;/script&gt;
  &lt;script src="https://ajax.googleapis.com/ajax/libs/jquerymobile/1.4.5/jquery.mobile.min.js"&gt;&lt;/script&gt;
  &lt;link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jquerymobile/1.4.5/jquery.mobile.min.css"&gt;
  &lt;?!= include('javascript'); ?&gt;
  
  &lt;div data-role="page" data-theme="a" id="main"&gt;
          &lt;div data-role="content"&gt;
              &lt;form id="myForm"&gt;
              
              &lt;div&gt;Date&lt;/div&gt;
              &lt;div&gt;&lt;select name="date" id="date"&gt;&lt;/select&gt;&lt;/div&gt;
              
              &lt;div&gt;Category&lt;/div&gt;
              &lt;div&gt;&lt;select name=category id="category" onchange="categoryChanged()" required&gt;&lt;/select&gt;&lt;/div&gt;
              
              &lt;div&gt;Amount&lt;/div&gt;
              &lt;div&gt;&lt;input type="text" name="amount" id="amount" required&gt;&lt;/div&gt;
              
              &lt;div&gt;Note&lt;/div&gt;
              &lt;div&gt;&lt;input type="text" name="note" id="note"&gt;&lt;/div&gt;
              
              &lt;div&gt;&lt;input type="button" id="submit" value="Submit" onclick="submitForm()"/&gt;&lt;/div&gt;
              
              &lt;/form&gt;
              
              &lt;!--&lt;a href="#dialog" data-role="button" data-rel="dialog" data-transition="pop"&gt;Dialog&lt;/a&gt;--&gt;
          &lt;/div&gt;&lt;!-- /content --&gt;
   
          &lt;div data-role="footer"&gt;                
              &lt;div id="status"&gt;&lt;/div&gt;
          &lt;/div&gt;&lt;!-- /footer --&gt;
  &lt;/div&gt;&lt;!-- /page --&gt;
  
  
  &lt;div data-role="page" id="dialog" data-close-btn="none"&gt;
    &lt;div data-role="header"&gt;
      &lt;h1 id="dialogHeading"&gt;Success!&lt;/h1&gt;
    &lt;/div&gt;
  
    &lt;div data-role="main" class="ui-content" id="dialogMain"&gt;
      &lt;p&gt;Text goes here.&lt;/p&gt;
    &lt;/div&gt;
    &lt;div class="ui-grid-b"&gt;
   &lt;div class="ui-block-a"&gt;&lt;/div&gt;
   &lt;div class="ui-block-b"&gt;&lt;a href="#main" data-role="button" data-icon="check"&gt;OK&lt;/a&gt;&lt;/div&gt;
   &lt;div class="ui-block-c"&gt;&lt;/div&gt;
     &lt;/div&gt;&lt;!-- /grid-a --&gt;
  
    &lt;!--&gt;&lt;div data-role="footer"&gt;&lt;/div&gt;--&gt;
  &lt;/div&gt; 
  
  &lt;script type="text/javascript"&gt;
  $(loadUI);
  &lt;/script&gt;</code></pre>
  </div>
  </div>
  </p>