adding a new row only when the last row in modified - html

Problem description:
I have a table with three rows. The first row contains a drop down. When a user selects a drop down option, a new row should be generated beneath the current last row. How can I tweak this code to such that a new row is generated only when the user selects a drop down option of the current last row, and not any other row?
JSFiddle: http://jsfiddle.net/JPVUk/13/
var ViewModel = function() {
var self = this;
self.items = ko.observableArray([{comment:'first comment', amount:0}]);
self.addNewItem = function(){
self.items.push(new Item('',0));
};
}
var Item = function(comment, amount) {
var self = this;
self.comment = ko.observable(comment);
self.amount = ko.observable(amount);
};
vm = new ViewModel()
ko.applyBindings(vm);
What I am struggling to do:
So, since I want to bind the change event to the last row, here's how I am approaching it:
<select class="input-small" data-bind="items()[items.length-1] ? event: { change: $root.addNewItem }">
This is however not working. Any ideas folks ?

Can't you just past the row that causes the event to fire to your handler and check it there?
Something like this:
<select class="input-small" data-bind="event: { change: $root.addNewItem }">
And then:
self.addNewItem = function(row){
if (row == self.items()[self.items().length - 1]) {
self.items.push(new Item('',0));
}
};

http://jsfiddle.net/JPVUk/14/
I'm not sure if jQuery was acceptable so this just uses DOM. Basically use the event object passed to knockout. Traverse a little dom and determine is the event target is a child of the last row in the parent table:
var tableRow = event.target.parentNode.parentNode,
body = tableRow.parentNode,
nodes = body.childNodes,
children = [];
for (var i = 0; i < nodes.length; i++) {
// remove non-element node types. ie textNodes, etc.
if (nodes[i].nodeType === 1) {
children.push(nodes[i]);
}
}
if (tableRow === children[children.length - 1]) {
self.items.push(new Item('', 0));
}

Related

Google APP script to convert value into TRUE for setting MultipleChoice answer key

I’ve searched a script that automates google form from my question banks (g-sheets).
All of my questions are multiple choice. I already added the setPoints() and setRequired(), however, for the answer key, I can’t find a way on how to script the value of my Answer’s column into TRUE when it meets the criteria. Below are the codes:
function getSpreadsheetData(sheetName) {
// This function gives you an array of objects modeling a worksheet's tabular data, where the first items — column headers — become the property names.
var arrayOfArrays = SpreadsheetApp.openById("GoogleSheetID").getSheetByName(sheetName || 'question').getDataRange().getValues();
console.log("Spreadsheet: " + arrayOfArrays[0]);
console.log("Spreadsheet: " + arrayOfArrays[1]);
var headers = arrayOfArrays.shift();
return arrayOfArrays.map(function (row) {
return row.reduce(function (memo, value, index) {
if (value) {
memo[headers[index]] = value;
}
return memo;
}, {});
});
}
function onOpen(e){
var form = FormApp.openById("GoogleFormID");
form.setTitle('DO ANY TITLE YOU WANT HERE');
form.setDescription('This is an example of Form generation');
getSpreadsheetData().forEach(function (row) {
var capitalizedName = row.Number.charAt(0).toUpperCase() + row.Number.slice(1);
console.log("Spreadsheet: " + capitalizedName);
form.addPageBreakItem()
.setTitle(capitalizedName);
var item = form.addMultipleChoiceItem();
item.setTitle(row.Questions)
.setPoints(1)
.setRequired(true)
.setChoices([
item.createChoice(row.Option1),
item.createChoice(row.Option2),
item.createChoice(row.Option3),
item.createChoice(row.Option4)
]);
});
}
function onSubmit(e) {
var form = FormApp.getActiveForm();
var items = form.getItems();
while(items.length > 0){
form.deleteItem(items.pop());
}
}
I’ve also search that to make choices the right answer, you just put TRUE or something like this:
item.createChoice(row.Option1, true),
item.createChoice(row.Option2, false),
item.createChoice(row.Option3, false),
item.createChoice(row.Option4, false)
However, this will only set Option1 as always the right answer. I want that TRUE or FALSE will be automatically place when found the right answer as stated in column G of my google sheets. Attach is my sheet:
GoogleSheet
This took me a while and I experience a weird issue, which is: After the Questions are set and the form saved, refreshing the page or trying to answer deletes all the options from one of the options (not always the same option).
I still have no clue why does this happen and I will investigate it later, but my code can give you an idea.
I changed your code a bit and added a new function which returns true or false for each option depending on the value of the G column:
form.addPageBreakItem()
.setTitle(capitalizedName);
var item = form.addMultipleChoiceItem();
item.setTitle(row.Questions)
.setPoints(1)
.setRequired(true)
.setChoices([
item.createChoice(row.Option1, checktrue(row, row.Option1)),
item.createChoice(row.Option2, checktrue(row, row.Option2)),
item.createChoice(row.Option3, checktrue(row, row.Option3)),
item.createChoice(row.Option4, checktrue(row, row.Option4))
]);
});
}
function checktrue(row, option){
var numRow = 2;
var sprsheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var colA = sprsheet.getRange("A2:A5").getValues();
for (var i = 0; i < colA.length; i++){
if (colA[i] == row.Number){
numRow += i;
}
}
var answer = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange("G"+numRow).getValue();
if (option == answer){
return true
} else {
return false
}
}

Can Google apps script be used to randomize page order on Google forms?

Update #2: Okay, I'm pretty sure my error in update #1 was because of indexing out of bounds over the array (I'm still not used to JS indexing at 0). But here is the new problem... if I write out the different combinations of the loop manually, setting the page index to 1 in moveItem() like so:
newForm.moveItem(itemsArray[0][0], 1);
newForm.moveItem(itemsArray[0][1], 1);
newForm.moveItem(itemsArray[0][2], 1);
newForm.moveItem(itemsArray[1][0], 1);
newForm.moveItem(itemsArray[1][1], 1);
newForm.moveItem(itemsArray[1][2], 1);
newForm.moveItem(itemsArray[2][0], 1);
...
...I don't get any errors but the items end up on different pages! What is going on?
Update #1:: Using Sandy Good's answer as well as a script I found at this WordPress blog, I have managed to get closer to what I needed. I believe Sandy Good misinterpreted what I wanted to do because I wasn't specific enough in my question.
I would like to:
Get all items from a page (section header, images, question etc)
Put them into an array
Do this for all pages, adding these arrays to an array (i.e: [[all items from page 1][all items from page 2][all items from page 3]...])
Shuffle the elements of this array
Repopulate a new form with each element of this array. In this way, page order will be randomized.
My JavaScript skills are poor (this is the first time I've used it). There is a step that produces null entries and I don't know why... I had to remove them manually. I am not able to complete step 5 as I get the following error:
Cannot convert Item,Item,Item to (class).
"Item,Item,Item" is the array element containing all the items from a particular page. So it seems that I can't add three items to a page at a time? Or is something else going on here?
Here is my code:
function shuffleForms() {
var itemsArray,shuffleQuestionsInNewForm,fncGetQuestionID,
newFormFile,newForm,newID,shuffle, sections;
// Copy template form by ID, set a new name
newFormFile = DriveApp.getFileById('1prfcl-RhaD4gn0b2oP4sbcKaRcZT5XoCAQCbLm1PR7I')
.makeCopy();
newFormFile.setName('AAAAA_Shuffled_Form');
// Get ID of new form and open it
newID = newFormFile.getId();
newForm = FormApp.openById(newID);
// Initialize array to put IDs in
itemsArray = [];
function getPageItems(thisPageNum) {
Logger.log("Getting items for page number: " + thisPageNum );
var thisPageItems = []; // Used for result
var thisPageBreakIndex = getPageItem(thisPageNum).getIndex();
Logger.log( "This is index num : " + thisPageBreakIndex );
// Get all items from page
var allItems = newForm.getItems();
thisPageItems.push(allItems[thisPageBreakIndex]);
Logger.log( "Added pagebreak item: " + allItems[thisPageBreakIndex].getIndex() );
for( var i = thisPageBreakIndex+1; ( i < allItems.length ) && ( allItems[i].getType() != FormApp.ItemType.PAGE_BREAK ); ++i ) {
thisPageItems.push(allItems[i]);
Logger.log( "Added non-pagebreak item: " + allItems[i].getIndex() );
}
return thisPageItems;
}
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
Logger.log('shuffle ran')
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
function shuffleAndMove() {
// Get page items for all pages into an array
for(i = 2; i <= 5; i++) {
itemsArray[i] = getPageItems(i);
}
// Removes null values from array
itemsArray = itemsArray.filter(function(x){return x});
// Shuffle page items
itemsArray = shuffle(itemsArray);
// Move page items to the new form
for(i = 2; i <= 5; ++i) {
newForm.moveItem(itemsArray[i], i);
}
}
shuffleAndMove();
}
Original post: I have used Google forms to create a questionnaire. For my purposes, each question needs to be on a separate page but I need the pages to be randomized. A quick Google search shows this feature has not been added yet.
I see that the Form class in the Google apps script has a number of methods that alter/give access to various properties of Google Forms. Since I do not know Javascript and am not too familiar with Google apps/API I would like to know if what I am trying to do is even possible before diving in and figuring it all out.
If it is possible, I would appreciate any insight on what methods would be relevant for this task just to give me some direction to get started.
Based on comments from Sandy Good and two SE questions found here and here, this is the code I have so far:
// Script to shuffle question in a Google Form when the questions are in separate sections
function shuffleFormSections() {
getQuestionID();
createNewShuffledForm();
}
// Get question IDs
function getQuestionID() {
var form = FormApp.getActiveForm();
var items = form.getItems();
arrayID = [];
for (var i in items) {
arrayID[i] = items[i].getId();
}
// Logger.log(arrayID);
return(arrayID);
}
// Shuffle function
function shuffle(a) {
var j, x, i;
for (i = a.length; i; i--) {
j = Math.floor(Math.random() * i);
x = a[i - 1];
a[i - 1] = a[j];
a[j] = x;
}
}
// Shuffle IDs and create new form with new question order
function createNewShuffledForm() {
shuffle(arrayID);
// Logger.log(arrayID);
var newForm = FormApp.create('Shuffled Form');
for (var i in arrayID) {
arrayID[i].getItemsbyId();
}
}
Try this. There's a few "constants" to be set at the top of the function, check the comments. Form file copying and opening borrowed from Sandy Good's answer, thanks!
// This is the function to run, all the others here are helper functions
// You'll need to set your source file id and your destination file name in the
// constants at the top of this function here.
// It appears that the "Title" page does not count as a page, so you don't need
// to include it in the PAGES_AT_BEGINNING_TO_NOT_SHUFFLE count.
function shuffleFormPages() {
// UPDATE THESE CONSTANTS AS NEEDED
var PAGES_AT_BEGINNING_TO_NOT_SHUFFLE = 2; // preserve X intro pages; shuffle everything after page X
var SOURCE_FILE_ID = 'YOUR_SOURCE_FILE_ID_HERE';
var DESTINATION_FILE_NAME = 'YOUR_DESTINATION_FILE_NAME_HERE';
// Copy template form by ID, set a new name
var newFormFile = DriveApp.getFileById(SOURCE_FILE_ID).makeCopy();
newFormFile.setName(DESTINATION_FILE_NAME);
// Open the duplicated form file as a form
var newForm = FormApp.openById(newFormFile.getId());
var pages = extractPages(newForm);
shuffleEndOfPages(pages, PAGES_AT_BEGINNING_TO_NOT_SHUFFLE);
var shuffledFormItems = flatten(pages);
setFormItems(newForm, shuffledFormItems);
}
// Builds an array of "page" arrays. Each page array starts with a page break
// and continues until the next page break.
function extractPages(form) {
var formItems = form.getItems();
var currentPage = [];
var allPages = [];
formItems.forEach(function(item) {
if (item.getType() == FormApp.ItemType.PAGE_BREAK && currentPage.length > 0) {
// found a page break (and it isn't the first one)
allPages.push(currentPage); // push what we've built for this page onto the output array
currentPage = [item]; // reset the current page to just this most recent item
} else {
currentPage.push(item);
}
});
// We've got the last page dangling, so add it
allPages.push(currentPage);
return allPages;
};
// startIndex is the array index to start shuffling from. E.g. to start
// shuffling on page 5, startIndex should be 4. startIndex could also be thought
// of as the number of pages to keep unshuffled.
// This function has no return value, it just mutates pages
function shuffleEndOfPages(pages, startIndex) {
var currentIndex = pages.length;
// While there remain elements to shuffle...
while (currentIndex > startIndex) {
// Pick an element between startIndex and currentIndex (inclusive)
var randomIndex = Math.floor(Math.random() * (currentIndex - startIndex)) + startIndex;
currentIndex -= 1;
// And swap it with the current element.
var temporaryValue = pages[currentIndex];
pages[currentIndex] = pages[randomIndex];
pages[randomIndex] = temporaryValue;
}
};
// Sourced from elsewhere on SO:
// https://stackoverflow.com/a/15030117/4280232
function flatten(array) {
return array.reduce(
function (flattenedArray, toFlatten) {
return flattenedArray.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
},
[]
);
};
// No safety checks around items being the same as the form length or whatever.
// This mutates form.
function setFormItems(form, items) {
items.forEach(function(item, index) {
form.moveItem(item, index);
});
};
I tested this code. It created a new Form, and then shuffled the questions in the new Form. It excludes page breaks, images and section headers. You need to provide a source file ID for the original template Form. This function has 3 inner sub-functions. The inner functions are at the top, and they are called at the bottom of the outer function. The arrayOfIDs variable does not need to be returned or passed to another function because it is available in the outer scope.
function shuffleFormSections() {
var arrayOfIDs,shuffleQuestionsInNewForm,fncGetQuestionID,
newFormFile,newForm,newID,items,shuffle;
newFormFile = DriveApp.getFileById('Put the source file ID here')
.makeCopy();
newFormFile.setName('AAAAA_Shuffled_Form');
newID = newFormFile.getId();
newForm = FormApp.openById(newID);
arrayOfIDs = [];
fncGetQuestionID = function() {
var i,L,thisID,thisItem,thisType;
items = newForm.getItems();
L = items.length;
for (i=0;i<L;i++) {
thisItem = items[i];
thisType = thisItem.getType();
if (thisType === FormApp.ItemType.PAGE_BREAK ||
thisType === FormApp.ItemType.SECTION_HEADER ||
thisType === FormApp.ItemType.IMAGE) {
continue;
}
thisID = thisItem.getId();
arrayOfIDs.push(thisID);
}
Logger.log('arrayOfIDs: ' + arrayOfIDs);
//the array arrayOfIDs does not need to be returned since it is available
//in the outermost scope
}// End of fncGetQuestionID function
shuffle = function() {// Shuffle function
var j, x, i;
Logger.log('shuffle ran')
for (i = arrayOfIDs.length; i; i--) {
j = Math.floor(Math.random() * i);
Logger.log('j: ' + j)
x = arrayOfIDs[i - 1];
Logger.log('x: ' + x)
arrayOfIDs[i - 1] = arrayOfIDs[j];
arrayOfIDs[j] = x;
}
Logger.log('arrayOfIDs: ' + arrayOfIDs)
}
shuffleQuestionsInNewForm = function() {
var i,L,thisID,thisItem,thisQuestion,questionType;
L = arrayOfIDs.length;
for (i=0;i<L;i++) {
thisID = arrayOfIDs[i];
Logger.log('thisID: ' + thisID)
thisItem = newForm.getItemById(thisID);
newForm.moveItem(thisItem, i)
}
}
fncGetQuestionID();//Get all the question ID's and put them into an array
shuffle();
shuffleQuestionsInNewForm();
}

AS3 datagrid - Hide a row

I'm using 2 comboboxes to filter a dataGrid that has been populated via csv file. The first combobox filters the columns and works fine:
//Listener and function for when the Agreement ID is selected
agreement_cb.addEventListener(Event.CHANGE, agreement);
function agreement(event:Event):void
{
//get the number of columns
var columnCount:Number = myGrid.getColumnCount();
for (var i:int=0; i<columnCount; i++)
{
myGrid.getColumnAt(i).visible = false;
}
var columnNumber:Number = agreement_cb.selectedItem.data;
myGrid.getColumnAt(columnNumber).visible = true;
myGrid.getColumnAt(0).visible = true;
myGrid.columns[0].width = 200;
}
But I can't find anything on how to get the same type of function to hide all of the rows except the one they select from the second drop-down (codes_cb).
Any help is appreciated...
UPDATE:
loadedData = myLoader.data.split(/\r\n|\n|\r/);
loadedData.pop();
for (var i:int=0; i<loadedData.length; i++)
{
var rowArray:Array = loadedData[i].split(",");
loadedData[i] = {"SelectAgreement":rowArray[0],"KSLTPROF0057":rowArray[1] .........};
}
loadedData.shift();
myGrid.columns = ["SelectAgreement", "KSLTPROF0057", ......];
import fl.data.DataProvider;
import fl.controls.dataGridClasses.DataGridColumn;
myGrid.dataProvider = new DataProvider(loadedData);
A DataGrid always shows all objects in its dataProvider, so to hide rows, you need to hide the data objects. Some classes that work as dataProviders have this functionality built in that makes this really easy (Any Class that implements IList can be operate as a dataProvider), however fl.data.DataProvider is not one of those classes.
So I will provide answers using both, if you can, I highly recommend using mx.collections.ArrayCollection over fl.data.DataProvider.
Section 1: fl.data.DataProvider
For this I'm assuming that your loadedData array is a class property, not declared in a function.
function agreement(event:Event):void
{
//your existing code here
var dataProvider:DataProvider = MyGrid.dataProvider as DataProvider;//recover the dataprovider
dataProvider.removeAll();//remove all rows
for (var x:int = 0; x<loadedData.length; x++)
{
if (loadedData[x] == "SELECTION MATCH") //insert here your selection criteria
{
dataProvider.addItem(loadedData[x]); //add it back into the dataProvider
}
}
}
function resetFilter():void
{
var dataProvider:DataProvider = MyGrid.dataProvider as DataProvider;//recover the dataprovider
dataProvider.removeAll(); //prevent duplication
dataProvider.addItems(loadedData);//reload all rows
}
Section 2: mx.collections.ArrayCollection
My reasoning for recommending this is because ArrayCollection already has the functions to do this without the risk of data being lost by objects losing scope, it also reduces the amount of code/operations you need to do. To do this we use ArrayCollection.filterFunction & ArrayCollection.refresh() to filter the "visible array" without changing the source.
private var dataProvider:ArrayCollection = new ArrayCollection(loadedData);
MyGrid.dataProvider = dataProvider;
function agreement(event:Event):void
{
//your existing code here
dataProvider.filterFunction = myFilterFunction;//use my filter
dataProvider.refresh();//refresh the visible list using new filter/sort
}
function resetFilter():void
{
dataProvider.filterFunction = null;//clear filter
dataProvider.refresh();//refresh the visible list using new filter/sort
}
function myFilterFunction(item:Object):Boolean
{
if (item == "SELECTION MATCH") return true;//insert your selection criteria here
else return false;
}
the filterFunction accepts a function and uses it to test each object in the ArrayCollection, the function has to return a Boolean, true for "Yes, display this object" and false for "Do not diplay".

GAS is it possible to replace getActiveDocument().getSelection() at once?

My User has the following selection in his Gdoc.
Now from the sidebar he wants to to replace the selection he made on the document. The GAS question is if it is possible to do that at once, something like:
var selection = DocumentApp.getActiveDocument().getSelection()
selection.replace("newtext")
Or do I have to loop through selection.getRangeElements() in order to delete them (or replace them) and than in someway place the new text in that position?
Not, that's not possible (well, if it is, it's not documented).
You have to loop through the selected elements, mainly because the selection may take part of paragraphs, forcing you to manage that. i.e. deleting just the selected part. And for completed selected elements, you can just remove them entirely (like images).
Here's an implementation on how to do this (part of the Kaylan's Translate script modified by me to properly replace images and partially selected paragraphs.
function replaceSelection(newText) {
var selection = DocumentApp.getActiveDocument().getSelection();
if (selection) {
var elements = selection.getRangeElements();
var replace = true;
for (var i = 0; i < elements.length; i++) {
if (elements[i].isPartial()) {
var element = elements[i].getElement().asText();
var startIndex = elements[i].getStartOffset();
var endIndex = elements[i].getEndOffsetInclusive();
var text = element.getText().substring(startIndex, endIndex + 1);
element.deleteText(startIndex, endIndex);
if( replace ) {
element.insertText(startIndex, newText);
replace = false;
}
} else {
var element = elements[i].getElement();
if( replace && element.editAsText ) {
element.clear().asText().setText(newText);
replace = false;
} else {
if( replace && i === elements.length -1 ) {
var parent = element.getParent();
parent[parent.insertText ? 'insertText' : 'insertParagraph'](parent.getChildIndex(element), newText);
replace = false; //not really necessary since it's the last one
}
element.removeFromParent();
}
}
}
} else
throw "Hey, select something so I can replace!";
}

post all values in select box /listbox

I have a selectbox on a form - which I've turned in to a list box by putting
<select id="Select1" name="D1" size="5" style="width: 220px">
I'm filling this select/listbox with values...
When I post the form how can I get all the values in the select box..is this possible or am I only able to get one that has been selected.
Trouble is I want all the values in the select (I'm not selecting any as such)
Any ideas?
Before submitting the form you can use some JavaScript to pull the items out of the select and put them into a hidden text field (as a delimited string)
For example, you can get the values using
var select1 = document.getElementById('select1');
var values = new Array();
for(var i=0; i < select1.options.length; i++){
values.push(select1.options[i].value);
}
var allValues = values.join(";");
alert(allValues);
Hope that helps.
How are you adding the values to the list box? Are they static or are they pulled from a database.
If you're pulling from the database I would create a function that you use to get the data and bind to the list box.
Then use that same function when you want to get those values after the post. You may have to use some hidden fields to pass along any parameters you use to get the values for the list box in the first place.
example:
function get_models_for_make(int make_id)
mydata_rs = SELECT name, id FROM models WHERE make_id = make_id
return mydata_rs
end
so you could use this data to bind the objects to your listbox and also use it to get the values later that you did bind to your list box.
for (int i = source.Items.Count - 1; i >= 0; i--)
{
ListItem item = source.Items[i];
if (moveAllItems)
item.Selected = true;
if (item.Selected)
{
// if the target already contains items, loop through
// them to place this new item in correct sorted order
if (target.Items.Count > 0)
{
for (int j = 0; j < target.Items.Count; j++)
{
if (target.Items[j].Text.CompareTo(item.Text) > 0)
{
target.Items.Insert(j, item);
item.Selected = false;
break;
}
}
}
// if item is still selected, it must be appended
if (item.Selected)
{
target.Items.Add(item);
item.Selected = false;
}
// remove the item from the source list
source.Items.Remove(item);
}
}