Google Spreadsheet: обновлять значение в боковой панели, когда в таблице выбрана следующая строка - PullRequest
1 голос
/ 30 октября 2019

Чтобы упростить аннотацию аудиофайлов в электронной таблице Google, я хотел бы внедрить аудиопроигрыватель на боковой панели, который автоматически воспроизводит аудиофайл, упомянутый как URL в строке таблицы. После прослушивания и ввода некоторой даты в этом ряду, я хотел бы перейти к следующему ряду и сделать то же самое. Таким образом, URL-адрес аудиофайла должен обновляться всякий раз, когда я выбираю новую строку, и весь процесс также должен быть быстрым, чтобы быстро прослушивать один звуковой файл за другим.

Я экспериментировалс решением, упомянутым в этом сообщении SO , но это решение основано на функции опроса с интервалом времени, что для меня нецелесообразно, поскольку оно периодически обновляет боковую панель. Для меня крайне важно обновить содержимое боковой панели только один раз.

enter image description here

Code.gs

var SIDEBAR_TITLE = 'Opnam lauschteren';

/**
 * Adds a custom menu with items to show the sidebar and dialog.
 *
 * @param {Object} e The event parameter for a simple onOpen trigger.
 */
function onOpen(e) {
  SpreadsheetApp.getUi()
      .createAddonMenu()
      .addItem('Opname lauschteren', 'showSidebar')
      .addToUi();
}

/**
 * Runs when the add-on is installed; calls onOpen() to ensure menu creation and
 * any other initializion work is done immediately.
 *
 * @param {Object} e The event parameter for a simple onInstall trigger.
 */
function onInstall(e) {
  onOpen(e);
}

/**
 * Opens a sidebar. The sidebar structure is described in the Sidebar.html
 * project file.
 */
function showSidebar() {
  var ui = HtmlService.createTemplateFromFile('Sidebar')
      .evaluate()
      .setSandboxMode(HtmlService.SandboxMode.IFRAME)
      .setTitle(SIDEBAR_TITLE);
  SpreadsheetApp.getUi().showSidebar(ui);
}

function getValues() {
  var app = SpreadsheetApp;
  var value = app.getActiveSpreadsheet().getActiveSheet().getActiveCell().getValue();
  Logger.log(value);
  return value;
}

function getRecord() {
  // Retrieve and return the information requested by the sidebar.
  var sheet = SpreadsheetApp.getActiveSheet();
  var data = sheet.getDataRange().getValues();
  var headers = data[0];
  var rowNum = sheet.getActiveCell().getRow();
  if (rowNum > data.length) return [];
  var record = [];
  for (var col=0;col<headers.length;col++) {
    var cellval = data[rowNum-1][col];
    // Dates must be passed as strings - use a fixed format for now
    if (typeof cellval == "object") {
      cellval = Utilities.formatDate(cellval, Session.getScriptTimeZone() , "M/d/yyyy");
    }
    // TODO: Format all cell values using SheetConverter library
    record.push({ heading: headers[col],cellval:cellval });
  }
  Logger.log(record);
  return record;
}

Sidebar.html

<!-- Use a templated HTML printing scriptlet to import common stylesheet. -->
<?!= HtmlService.createHtmlOutputFromFile('Stylesheet').getContent(); ?>

<!-- Below is the HTML code that defines the sidebar element structure. -->
<div class="sidebar branding-below">
  <!-- The div-table class is used to make a group of divs behave like a table. -->
  <div class="block div-table" id="sidebar-record-block">
  </div>
  <div class="block" id="sidebar-button-bar">
  </div>
  <div id="sidebar-status"></div>

  <!-- Use a templated HTML printing scriptlet to import JavaScript. -->
<?!= HtmlService.createHtmlOutputFromFile('SidebarJavaScript').getContent(); ?>
</div>

<!-- Enter sidebar bottom-branding below. -->
<div class="sidebar bottom">
  <span class="gray branding-text">PG</span>
</div>

SidebarJavaScript.html

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
  /**
   * Run initializations on sidebar load.
   */
  $(function() {
    // Assign handler functions to sidebar elements here, if needed.

    // Call the server here to retrieve any information needed to build
    // the dialog, if necessary.

    // Start polling for updates        
    poll();
  });

  /**
   * Poll a server-side function at the given interval, to have
   * results passed to a successHandler callback.
   *
   * https://stackoverflow.com/a/24773178/1677912
   *
   * @param {Number} interval   (optional) Time in ms between polls.
   *                            Default is 2s (2000ms)
   */
  function poll(interval) {
    interval = interval || 3000;
    setTimeout(function() {
      google.script.run
        .withSuccessHandler(showRecord)
        .withFailureHandler(
          function(msg, element) {
            showStatus(msg, $('#button-bar'));
            element.disabled = false;
          })
        .getRecord();
    }, interval);
  };

  /**
   * Callback function to display a "record", or row of the spreadsheet.
   *
   * @param {object[]}  Array of field headings & cell values
   */
  function showRecord(record) {
    if (record.length) {
      for (var i = 2; i <= 2; i++) {
        // build field name on the fly, formatted field-1234
        var str = '' + i;
        var fieldId = 'field-' + ('0000' + str).substring(str.length)

        // If this field # doesn't already exist on the page, create it
        if (!$('#'+fieldId).length) {
          var newField = $($.parseHTML('<div id="'+fieldId+'"></div>'));
          $('#sidebar-record-block').append(newField);
        }

        // Replace content of the field div with new record
        $('#'+fieldId).replaceWith('<div id="'+fieldId+'" class="div-table-row"></div>');
        $('#'+fieldId).append($('<div class="div-table-th">' + record[i].heading + '</div>'))
                      .append('<audio id="player" controls > <source src=' + record[i].cellval + ' type=audio/wav >      Your browser does not support the audio element.    </audio>');
      }
    }

    // TODO: hide any existing fields that are beyond the current record length

    //Setup the next poll
    poll();
  }

  /**
   * Displays the given status message in the sidebar.
   *
   * @param {String} msg The status message to display.
   * @param {String} classId The message type (class id) that the message
   *   should be displayed as.
   */
  function showStatus(msg, classId) {
    $('#sidebar-status').removeClass().html(msg);
    if (classId) {
      $('#sidebar-status').addClass(classId);
    }
  }

</script>

Воспроизводимый пример доступен здесь ;Дополнения> «Воспроизвести аудио» (необходима учетная запись Google).

Я изо всех сил пытаюсь найти способ инициировать обновление боковой панели только один раз и только при выборе новой строки. Использование боковой панели не является обязательным, скорее будет полезно другое решение, например, с автоматически обновляемой кнопкой «Play».

Ответы [ 2 ]

1 голос
/ 31 октября 2019

Я внес несколько небольших изменений в приведенный вами пример кода, чтобы боковая панель не обновлялась периодически после интервала времени.

В основном я использовал PropertiesService для хранения строкиэто выбрано. Идея состоит в том, что скрипт проверяет, совпадают ли текущая выбранная строка и ранее выбранная строка (выбранная в последний раз getRecord, то есть в течение последнего интервала). Если они одинаковы, выбор строки не изменился, что означает, что аудио на боковой панели не нуждается в обновлении.

Таким образом, он обновляется только при изменении выбранной строки, что, я думаю, является основной проблемой, с которой вы сталкиваетесь.

Чтобы достичь этого, ваш код должен быть изменен следующим образом(смотрите подробности об изменениях во встроенных комментариях):

getRecord ()

function getRecord() {
  var scriptProperties = PropertiesService.getScriptProperties();
  var sheet = SpreadsheetApp.getActiveSheet();
  var data = sheet.getDataRange().getValues();
  var headers = data[0];
  var rowNum = sheet.getActiveCell().getRow(); // Get currently selected row
  var oldRowNum = scriptProperties.getProperty("selectedRow"); // Get previously selected row
  if(rowNum == oldRowNum) { // Check if the was a row selection change
    // Function returns the string "unchanged"
    return "unchanged";
  }
  scriptProperties.setProperty("selectedRow", rowNum); // Update row index
  if (rowNum > data.length) return [];
  var record = [];
  for (var col=0;col<headers.length;col++) {
    var cellval = data[rowNum-1][col];
    if (typeof cellval == "object") {
      cellval = Utilities.formatDate(cellval, Session.getScriptTimeZone() , "M/d/yyyy");
    }
    record.push({ heading: headers[col],cellval:cellval });
  }
  return record;
}

В зависимости от того, было ли изменение выбора, getRecord возвращает:

  • a record массив, если выбранная строка отличается.
  • строка "unchanged", если выбранная строка совпадает. Возможно, это не самый элегантный способ справиться с этим, но вы поняли идею.

Затем showRecord(record) возвращает это возвращаемое значение. Если это значение является строкой "unchanged", она не будет обновлять боковую панель:

showRecord (запись)

  function showRecord(record) {
    // Checks whether returned value is `"unchanged"` (this means the row selected is the same one as before)
    if (record != "unchanged" && record.length) {
      for (var i = 2; i <= 2; i++) {
        // build field name on the fly, formatted field-1234
        var str = '' + i;
        var fieldId = 'field-' + ('0000' + str).substring(str.length)

        // If this field # doesn't already exist on the page, create it
        if (!$('#'+fieldId).length) {
          var newField = $($.parseHTML('<div id="'+fieldId+'"></div>'));
          $('#sidebar-record-block').append(newField);
        }

        // Replace content of the field div with new record
        $('#'+fieldId).replaceWith('<div id="'+fieldId+'" class="div-table-row"></div>');
        $('#'+fieldId).append($('<div class="div-table-th">' + record[i].heading + '</div>'))
                      .append('<audio id="player" controls autoplay> <source src=' + record[i].cellval + ' type=audio/wav >      Your browser does not support the audio element.    </audio>');
      }
    }

    // TODO: hide any existing fields that are beyond the current record length

    //Setup the next poll
    poll();
  }

Я также добавил autoplayатрибут в этой строке:

.append('<audio id="player" controls> <source src=' + record[i].cellval + ' type=audio/wav >      Your browser does not support the audio element.    </audio>')

, чтобы звук воспроизводился автоматически при выборе новой строки, без необходимости нажимать кнопку play.

Наконец, я изменил poll интервал до 500, чтобы вам не приходилось так долго ждать, пока воспроизводится новый звук. В любом случае вы можете отредактировать это так, как вам удобно:

interval = interval || 500;

Я не модифицировал оставшуюся часть сценария, хотя, вероятно, его можно улучшить, поскольку он был написан в основном для другого сценария. выпуск.

Надеюсь, это поможет.

0 голосов
/ 31 октября 2019

Воспроизведение моей музыки

Я добавил кнопку воспроизведения для каждого выбора плейлиста. Возможно, это поможет вам достичь желаемого.

code.gs:

function onOpen() {
  SpreadsheetApp.getUi().createMenu('My Music')
  .addItem('Launch Music', 'launchMusicDialog')
  .addToUi();
}

function convMediaToDataUri(filename){
  var filename=filename || "You Make Loving Fun.mp3";//this was my debug song
  var folder=DriveApp.getFolderById("Music Folder Id");
  var files=folder.getFilesByName(filename);
  var n=0;
  while(files.hasNext()) {
    var file=files.next();
    n++;
  }
  if(n==1) {
    var blob=file.getBlob();
    var b64DataUri='data:' + blob.getContentType() + ';base64,' + Utilities.base64Encode(blob.getBytes());
    Logger.log(b64DataUri)
    var fObj={filename:file.getName(),uri:b64DataUri}
    return fObj;
  }
  throw("Multiple Files with same name.");
  return null;
}


function launchMusicDialog() {
  var userInterface=HtmlService.createHtmlOutputFromFile('music1');
  SpreadsheetApp.getUi().showModelessDialog(userInterface, 'Music');
}


function doGet() {
  return HtmlService.createHtmlOutputFromFile('music1').addMetaTag('viewport', 'width=device-width, initial-scale=1');
}

function getPlaylist() {
  var ss=SpreadsheetApp.getActive();
  var sh=ss.getSheetByName('MusicList');
  var rg=sh.getRange(2,1,sh.getLastRow()-1,sh.getLastColumn());
  var vA=rg.getValues();
  var pl=[];
  var idx=0;
  var html='<style>th,td{border:1px solid black;}</style><table><tr><th>Index</th><th>Item</th><th>FileName</th><th>&nbsp;</th></tr>';
  for(var i=0;i<vA.length;i++) {
    if(vA[i][4]) {
      pl.push(vA[i][1]);
      html+=Utilities.formatString('<tr><td>%s</td><td>%s</td><td>%s</td><td><input type="button" value="Play This" onClick="playThis(%s)" /></td></tr>',idx,vA[i][0],vA[i][1],idx++);
    }
  }
  html+='</table>';
  return {playlist:pl,html:html};
}

music1.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
    <style>
      label{margin:2px 10px;}
    </style>
  </head>
  <script>
    var selectionList=["BarbaraAnn.mp3","Don't Let Me Come Home a Stranger.mp3"];
    var gVolume=0.2;
    var index=0;
    $(function(){
       document.getElementById('msg').innerHTML="Loading Playlist";
       google.script.run
       .withSuccessHandler(function(Obj){
         selectionList=Obj.playlist;
         console.log(Obj.playlist);
         document.getElementById('list').innerHTML=Obj.html;
         google.script.run
         .withSuccessHandler(function(fObj){
           $('#audio1').attr('src',fObj.uri);
           var audio=document.getElementById("audio1");
           audio.volume=gVolume;
           audio.onended=function() {
             document.getElementById('status').innerHTML='Ended...';
             playnext();
           }
           var msg=document.getElementById('msg');
           msg.innerHTML="Click play to begin playlist. Additional selections will begin automatically";        
           audio.onplay=function() {
             document.getElementById('msg').innerHTML='Playing: ' + selectionList[index-1];
             document.getElementById('status').innerHTML='Playing...';
             document.getElementById('skipbtn').disabled=false;
           }
           audio.onvolumechange=function(){
             gVolume=audio.volume;
           }         
         })
         .convMediaToDataUri(selectionList[index++]);
       })
       .getPlaylist();
    });

    function playnext() {
      if(index<selectionList.length) {
        document.getElementById('status').innerHTML='Loading...';
        document.getElementById('msg').innerHTML='Next Selection: ' + selectionList[index];
        google.script.run
        .withSuccessHandler(function(fObj){
          $('#audio1').attr('src',fObj.uri);
          var audio=document.getElementById('audio1');
          audio.volume=gVolume;
          audio.play();
        })
        .convMediaToDataUri(selectionList[index++]);
      }else{
        document.getElementById('status').innerHTML='Playlist Complete';
        document.getElementById('msg').innerHTML='';
        document.getElementById('cntrls').innerHTML='<input type="button" value="Replay Playlist" onClick="replayPlaylist()" />';
      }
    }
   function replayPlaylist() {
     index=0;
     document.getElementById('cntrls').innerHTML='';
     playnext();
   }
   function skip() {
     var audio=document.getElementById('audio1');
     document.getElementById('skipbtn').disabled=true;
     audio.pause();
     playnext();
   }
   function playThis(idx) {
     index=idx;
     var audio=document.getElementById('audio1');
     //audio.pause();
     playnext();
   }
  </script>
  <body>
    <div id="msg"></div>
    <audio controls id="audio1" src=""></audio><br />
    <div id="status"></div>
    <div><input type="button" id="skipbtn" value="Skip" onClick="skip()" disabled /></div>
    <div id="cntrls"></div>
    <div id="list"></div>
  </body>
</html>

По общему признанию, переход немного грубоватно я не приложил столько усилий для модификации, так что, возможно, вы можете немного сгладить ее. Просто запустите launchMusicDiaog(), чтобы начать. Там также есть doGet () для веб-приложения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...