rebol/red: Is there an elegant way to know if it is last item in foreach? - rebol

My code is clunky:
length: length? items
count: 0
foreach item items [
count: count + 1
if count = length [
print "last item"
]
]
Is there something better ?

Typically people use FORALL (which would be much better named FOR-NEXT) which moves the series position as opposed to giving an item, then you can test it with TAIL?. The downside is you have to pick the item out of the series at the current position:
forall items [
probe items/1 ;-- how to access current item
if tail? next items [ ;-- could use LAST? ITEMS in Rebol 3
print "last item"
]
]
This is approximately equivalent to:
if not tail? items [
original: items
until [
probe items/1
if tail? next items [
print "last item"
]
items: next items
tail? items
]
items: original
]
Be forewarned: FORALL mutates its input series and tries to put it back to the initial position at the end. But it has poorly-defined behavior in the case of errors being raised, so you could leave your input in mid-iteration if there is a problem.

Related

Google Docs API for creating invoice containing table of variable number of rows

I have a template file for my invoice with a table with sample row, but I want to add more rows dynamically based on a given array size, and write the cell values from the array...
Template's photo
I've been struggling for almost 3 days now.
Is there any easy way to accomplish that?
Here's the template file: Link to the Docs file(template)
And here's a few sample arrays of input data to be replaced in the Template file:
[
[
"Sample item 1s",
"Sample Quantity 1",
"Sample price 1",
"Sample total 1"
],
[
"Sample item 2",
"Sample Quantity 2",
"Sample price 2",
"Sample total 2"
],
[
"Sample item 3",
"Sample Quantity 3",
"Sample price 3",
"Sample total 3"
],
]
Now, the length of the parent array can vary depending on the number of items in the invoice, and that's the only problem that I'm struggling with.
And... Yeah, this is a duplicate question, I've found another question on the same topic, but looking at the answers and comments, everyone is commenting that they don't understand the question whereas it looks perfectly clear for me.
Google Docs Invoice template with dynamically items row from Google Sheets
I think the person who asked the question have already quit from it. :(
By the way I am using the API for PHP (Google API Client Library for PHP), and code for replacing dummy text a Google Docs Document by the actual data is given below:
public function replaceTexts(array $replacements, string $document_id) {
# code...
$req = new Docs\BatchUpdateDocumentRequest();
// var_dump($replacements);
// die();
foreach ($replacements as $replacement) {
$target = new Docs\SubstringMatchCriteria();
$target->text = "{{" . $replacement["targetText"] . "}}";
$target->setMatchCase(false);
$req->setRequests([
...$req->getRequests(),
new Docs\Request([
"replaceAllText" => [
"replaceText" => $replacement["newText"],
"containsText" => $target
]
]),
]);
}
return $this->docs_service->documents->batchUpdate(
$document_id,
$req
);
}
A possible solution would be the following
First prep the document by removing every row from the table apart from the title.
Get the full document tree from the Google Docs API.
This would be a simple call with the document id
$doc = $service->documents->get($documentId);
Traverse the document object returned to get to the table and then find the location of the right cell. This could be done by looping through the elements in the body object until one with the right table field is found. Note that this may not necessarily be the first one since in your template, the section with the {{CustomerName}} placeholder is also a table. So you may have to find a table that has the first cell with a text value of "Item".
Add a new row to the table. This is done by creating a request with the shape:
[
'insertTableRow' => [
'tableCellLocation' => [
'rowIndex' => 1,
'columnIndex' => 1,
'tableStartLocation' => [
'index' => 177
]
]
]
]
The tableStartLocation->index element is the paragraph index of the cell to be entered, i.e. body->content[i]->table->startIndex. Send the request.
Repeat steps 2 and 3 to get the updated $doc object, and then access the newly created cell i.e. body->content[i]->table->tableRows[j]->tableCells[k]->content->paragraph->elements[l]->startIndex.
Send a request to update the text content of the cell at the location of the startIndex from 5 above, i.e.
[
'insertText' => [
'location' => [
'index' => 206,
]
],
'text' => 'item_1'
]
]
Repeat step 5 but access the next cell. Note that after each update you need to fetch an updated version of the document object because the indexes change after inserts.
To be honest, this approach is pretty cumbersome, and it's probably more efficient to insert all the data into a spreadsheet and then embed the spreadsheet into your word document. Information on that can be found here How to insert an embedded sheet via Google Docs API?.
As a final note, I created a copy of your template and used the "Try this method" feature in the API documentation to validate my approach so some of the PHP syntax may be a bit off, but I hope you get the general idea.

Find item in list and add to other list

I have a model containing a list of items that are rendered in a select as options.
The user can select an item, enter a number and click add to add the selected item and a "quantity" to a list.
My model looks like this:
type alias Drink =
{ id: String
, name: String
}
type alias Item =
{ id: String
, quantity: Int
}
type alias Model =
{ drinks: List Drink
, selected: List Item
, inputDrink: String
, inputQuantity: Int
}
I then want to render the selected list in a table. My main struggle right now is figuring out how I map over the array of selected items, based on the id of the current item find the name of the Drink to render in the table.
I've made this itemRow view:
itemRow : (Item, Drink) -> Html Msg
itemRow tuple =
-- This bit not updated to work with a Tuple yet.
tr [ id item.id ]
[ td []
[ button [] [ text "x" ]
]
, td [] [ text drink.name ]
, td []
[ input [ type_ "number", value (String.fromInt item.quantity) ] []
]
]
So what I'd like is to do something like:
model.selected
|> List.map (\selected -> (selected, List.Extra.find (\drink -> drink.id == selected.id)) )
|> List.map itemRow
But to do this I need to get rid of the Maybe I get from List.Extra.find and I don't know how… 😅
Any other tips or tricks on how I might better solve this by modelling the data differently very welcome. New to Elm :)
Here's how you remove the Nothings. Although you know that the find must always succeed, Elm requires you to handle the case where it does not. Here I just ignore those cases.
model.selected
|> List.filterMap (\selected ->
case List.Extra.find (\drink -> drink.id == selected.id) of
Just x -> Just (selected, x)
Nothing -> Nothing
)
|> List.map itemRow

In ramda.js, does fromPairs change the order of elements?

I receive large array of pairs:[number, {number,number, big array of numbers}]
First I add my main pair to beginning of the array:
prepend([target[0], {count : target[1].length, overall : target[1].length, items:target[1]}]),
Next I do:
Promise.all([
to_file_json(
token.user_id,
'connections_data',
JSON.stringify(
fromPairs(r[0])
))...
And I can find my main pair somewhere in the middle of my file.
So my question is, could fromPairs possibly change the order? If yes what can I do to prevent this?
Edit:
additional info:
1)r variable correspond to [[number, Friends][], Float64Array[]]
2) target variable correspond to [number,number[]]
3) Beginning of element which i'm prepend, it's always the biggest one, and it got in the middle of the file somehow.
"136444868":{"count":304,"overall":304,"items":[19363,234566,290677,1375661,2030175,2131497,2593602,2596894,2816890,2869895,3170377,3437884,3486703,3504543,4046799,4235623,5366101.....
4) Friends type :
interface Friends {
count:number,
overall:number,
items:number[]
};
sample data
{
"19363":{"count":5,"overall":3088,"items":[51177198,53119509,136035431,209482119,216378147]}
,"234566":{"count":6,"overall":6803,"items":[290677,3504543,23180680,75311610,178479726,196401211]}
,"290677":{"count":19,"overall":2213,"items":[234566,5686439,7873089,11175816,13726459,20697213,23180680,27419631,55209039,74493674,75311610,125041200,133272552,139307068,159591583,168386810,173599247,178429642,189097165]}
,"1375661":{"count":0,"overall":76,"items":[]},"2030175":{"count":14,"overall":86,"items":[2596894,6507568,11681736,17736119,49557638,117771194,127144880,141523415,147264238,153044182,156925389,160656334,223530741,262311445]},"2131497":{"count":16,"overall":301,"items":[13598979,15682478,20357560,20869716,27419631,30869837,33650605,40129023,68976427,88146695,90648231,101105191,118193129,145163503,216503667,387266562]},
I expect the issue is that you do a prepend without removing the element from its later place in the list.
Then you might end up with some data like:
[
[ 2131497, { count: 16, overall: 301, items: [ /* .. * ] } ], // duplicate
[ 19363, { count: 5, overall: 3088, items: [ /* .. * ] } ],
[ 234566, { count: 6, overall: 6803, items: [ /* .. * ] } ],
[ 290677, { count: 19, overall: 2213, items: [ /* .. * ] } ],
[ 1375661, { count: 0, overall: 76, items: [ /* .. * ] } ],
[ 2030175, { count: 14, overall: 86, items: [ /* .. * ] } ],
[ 2131497, { count: 16, overall: 301, items: [ /* .. * ] } ] // duplicate
]
Then, when you do fromPairs, the later version will override the earlier one, and it will end up back in the list at the original position, as per this line from the documentation:
If a key appears in multiple pairs, the rightmost pair is included in the object.
But... even if you fix this, you will still not get the behavior you want, because of the object property iteration order specification, which says that integer keys of an object are iterated first, in numeric order, before the non-integer keys. Axel Rauschmayer has a very readable description of this.
These complexities are one of the reasons that Ramda (disclaimer: I'm one of the authors) has not created a foldObj implementation.

Rebol text fields - checking values and changing colors

In the following prototype test code, I'm trying to create a comparison system that compares two fields, and colors them depending on whether they are equal or not.
comparecolors: [
either answer-user/text = answer-correct/text [
answer-user/font/color: green
answer-correct/font/color: green
show answer-user
show answer-correct
][
answer-user/font/color: red
answer-correct/font/color: black
show answer-user
show answer-correct
]
]
view layout [
answer: field [
answer-user/text: copy answer/text
do comparecolors
show answer
focus answer
show answer-user
]
label "Compare"
answer-user: info
answer-correct: info
across
text-list "Hello" "Goodbye" "Boy" "Girl" "Soldier" [
answer-correct/text: copy value
do comparecolors
show answer-correct
]
]
Some problems I am having:
The green color is affecting all the fields instead of just the ones I am specifying.
The red color is not working when the two fields are not equal.
The system does not check for none! value (I know it is not written so in the above code, but I tried some ways that didn't work, so I don't really know how to go about it).
Whenever you see multiple fields affected when you change the attribute of only one, it means that VID has made an optimization so that all those fields are sharing the same data structure, and in this the same font structure. So, we need to force VID to allocate a new font structure like this:
change-colors: func [ user [object!] correct [object!]
/local u c
][
set [ u c ]
either user/text = correct/text [
[ green green ]
][
[ red black ]
]
user/font/color: get u
correct/font/color: get c
show [ user correct ]
]
view layout [
answer: field [
answer-user/text: copy answer/text
change-colors answer-user answer-correct
focus answer
] font-color black
label "Compare"
answer-user: info font-color black
answer-correct: info font-color black
across
text-list "Hello" "Goodbye" "Boy" "Girl" "Soldier" [
answer-correct/text: copy value
change-colors answer-user answer-correct
]
]

REBOL 3 - How to update a layout that has already been viewed?

I'm trying to add a field to a layout after it has been viewed
view/no-wait m: [field "hello"]
insert tail m 'field
insert tail m "hello"
update-face m
** Script error: update-face does not allow block! for its face argument
I want to update the whole layout, not just the field or some part of it. If I try to use
view m, it opens a new window. Do I just have to un-view it and then view again?
You can use the LAYOUT function in R3-GUI as well. See the example below:
view/no-wait m: layout [field "hello"]
;We need to get the BACKDROP container which is first sub-face in the WINDOW face
m: first faces? m
append-content m [
field "world"
]
do-events
Ofcourse there are also other ways how to handle layout content dynamically.
Try this example from Richard
REBOL [
Title: "Layouts example #20"
Author: "Richard Smolak"
Version: "$Id: layouts-20.r3 852 2010-10-07 13:28:26Z cyphre $"
]
stylize [
tbox: hpanel [
about: "Simple rectangular box."
facets: [
init-hint: 200x200
min-hint: 0x0
max-hint: guie/max-pair
break-after: 1
]
options: [
init-hint: [pair!]
]
actors: [
on-make: [
append face/options [
content: [
button "hello" on-action [print "hello"]
button "world" on-action [print "hello"]
]
]
do-actor/style face 'on-make none 'hpanel
]
]
draw: [
pen red
fill-pen blue
box 0x0 (viewport-box/bottom-right - 1)
]
]
]
view [
test: tbox
button "clear"
on-action [
clear-content test
]
button "set"
on-action [
set-content test [
button "test"
field "the best"
]
]
button "insert"
on-action [
insert-content test bind/set probe reduce [to-set-word copy/part random "abcdefgh" 2 'button join "button #" 1 + length? test/gob] 'system
]
button "append"
on-action [
append-content test reduce ['button join "button #" 1 + length? test/gob]
]
button "remove 2 faces at pos 3"
on-action [
remove-content/pos/part test 3 2
]
]
so the words you're looking for are append-content and insert-content which take a face and a block as parameters where the block contains the definition of another face.
I don't know view yet, but I have a hint. The first line sets "m" to the block [field "hello"]. Check to see what "update-face" expects...