I want to make a function in my app that allows you to like/save items (each item can only be saved once, so its not a shopping cart idea). I want the saved items to appear on a different page once they are saved, in a list layout. I also want the user to be able to easily delete items out of this list, so the layout does not get impacted. The items (shown in a swipe carousel) and their like-buttons are already there.
How to make an 'add to favourites' function
You will need to use global variables to store and access data across pages. You donât mention repeaters, but this post is tagged with ârepeater-widgetâ. Sharing data between repeaters on different pages can be more complex, because you need to build or update the repeater automatically on page load. The easiest way to do this is to have the same basic repeaters on both pages. They donât have to be identical in terms of layout or repeater widgets, but should have the same list of items (number of rows) and a common way to refer to each item in both repeaters, like an index, product number, or unique name.
For example, letâs say on Page 1 you have a carousel of 5 items, represented as a horizontal repeater of 5 rows. When a user likes an item, a corresponding global variable can be set. For the sake of simplicity, you could have 5 global variables, Fav1, Fav2, Fav3, Fav4, Fav5. When the user clicks the Like button in row 2, it can set Fav2 to âtrueâ.
On Page 2 letâs say you want to show a vertical list of all liked items from the carousel on Page 1.
You can have a repeater on Page 2 with the same 5 rows (but styled to show the list vertically, maybe less or more info per item, etc. but the same basic items in the same order.) Then, you can have a series of conditional cases that tests the value of Fav1, Fav2, etc. and shows the corresponding row if the value is âtrueâ by applying a filter. Do this in the Page Loaded event to make it automaticâsomething like,
Page Loaded
Case 1
If value of Fav1 equals âtrueâ
Add Filter
MyRepeater add [[Item.index == 1]]
Case 2
If value of Fav2 equals âtrueâ
Add Filter
MyRepeater add [[Item.index == 2]]
⌠etc. for all rows
Iâm not sure what this means⌠If you delete a list item, that means it gets removed, which typically changes the layout of the list, especially in a repeater. So, could you please clarify what you mean by âdelete itemsâ and maybe which list⌠the original list of items that can be liked, or the subsequent list of liked items? âŚOr maybe âdeletingâ really means just clicking the Like button again to deselect it? Either way, how would the layout not get impacted? Maybe you could share some examples or sketches of layouts for various states?
If you could reply here and upload a sample .rp file it will be much easier for forum users to provide solutions tailored for your needs.
Here is a demo I made for you, based off one I made a few years ago.
repeater favorites across pages.rp (158.5 KB)
-
On Page 1, there is a repeater with a few items. Clicking an item âlikesâ it, showing a âfavorite heartâ icon. Clicking again âunlikesâ it. This prototype uses one global variable, VarFavorites, to store which items are liked. As each item is liked (selected) it adds itself to VarFavorites, thus creating an array (a âlistâ as the variable value.) When unselected it removes its value from VarFavorites. (This is done with built-in string functions that you can inspect in the interaction code.) This is a more extensible way to represent liked items; you can add or remove items from the repeater and the code will still work (just be sure to add the same rows to the repeater on Page 2.)
-
On Page 2, there is a corresponding repeater, prebuilt with the same items as the repeater on Page 1. It has a Loaded event (triggered after all its items are loaded) that adds a filter to itself, hiding all the rows by default.)
- The Page Loaded event copies the value of VarFavorites to OnLoadVariable (the built-in global variable). Then it triggers a recursive subroutine to filter the repeater based on which items have been likedâthe values in OnLoadVariable. The subroutine is represented as a dynamic panel, âShowFavsâ, with two empty states. Each time ShowFavs changes state it looks at the first value in OnLoadVariable, and updates the corresponding repeater row, setting the âFavâ column cell to âtrueâ, then pops that value off of OnLoadVariable, then changes its state to Next, wrap (making it a recursive function/subroutine.) In this way, it walks through each liked item until there are no more items to processâthus the value of OnLoadVariable is blank. When this happens, it adds a filter to the repeater with the rule,
[[Item.Fav == 'true']] remove other filters
which results in showing only the liked/favorite items. Note that the original set of liked items is still stored in VarFavorites (which comes in handy if user needs to navigate back to Page 1.) - There are a set of test buttons which can help demonstrate how things work. They are for learning purposes only and can be deleted.
- The Page Loaded event copies the value of VarFavorites to OnLoadVariable (the built-in global variable). Then it triggers a recursive subroutine to filter the repeater based on which items have been likedâthe values in OnLoadVariable. The subroutine is represented as a dynamic panel, âShowFavsâ, with two empty states. Each time ShowFavs changes state it looks at the first value in OnLoadVariable, and updates the corresponding repeater row, setting the âFavâ column cell to âtrueâ, then pops that value off of OnLoadVariable, then changes its state to Next, wrap (making it a recursive function/subroutine.) In this way, it walks through each liked item until there are no more items to processâthus the value of OnLoadVariable is blank. When this happens, it adds a filter to the repeater with the rule,
-
Back on Page 1, the liked items are restored in the repeater (if a user navigates back to this page from Page 2 or elsewhere) via the Page Loaded event.
- OnLoadVariable is set to the value of VarFavorites.
- VarFavorites is then set to blank. It will be rebuilt as items are set to âLikeâ.
- Because this repeater is structured a little different from the Page 2 repeater, it doesnât need the recursive subroutine. Rather, a âlistener eventâ is set up in the ârowGroupâ widget group in the repeater. Its Moved event âlistensâ to OnLoadVariable, and if it âhearsâ its number, it sets its selection state to âtrueâ, marking it as liked (just the same as if a user clicked it âŚwhich they actually already did the last time they were on Page 1).
Thatâs pretty much how this works. Reply with any further questions about details, or if anything should behave differently and you canât figure out how to do it.
If your Liked/Saved list on Page 2 is not a repeater, then you can create a set of items to match those on Page 1, hide them all by default, then use Page Loaded with conditional cases to show certain items, based on global variable values.
If your lists are dynamic repeaters, meaning the user (or your interaction code) needs to edit, add, or remove rows on Page 1, you can use similar logic to add rows to the repeater on Page 2. If it is possible to add new items (rows) or edit data, e.g., maybe a name or comment field, and you want to transfer this to the list on Page 2, then youâll need to store all that data in the global variable(s) on Page 1, then add rows to a blank repeater on Page 2 using the data in the global variables.
Here is a post with examples of how to do this more advanced and more extensible repeater copying across pages:
Hi,
Thanks a lot for the reply!!!, i will try it later today!!
Because you asked for a file, here it is:
Testcardcarousel_addtofavourites.rp (142.0 KB)
Iâm making an app with conversation cards, so on each card is a question. I want the user to be able to save any card by clicking on the heart icon. I have yet to make an interaction where it is filled with pink when clicked. When liked, the cards appear on the âliked cardsâ page. What i mean by that i dont want the layout to be impacted when deleting a card, is that when you delete the middle card, there doesnât appear a gap in the list layout. I hope this makes it clearer
I also stumbled upon something else. I noticed that the counter i have made above the conversation cards carousel doesnt work properly. It should always show â1/20â when the card with question 1 is shown. Do you know how to fix this issue?
Thanks a lot in advance!
Thanks for posting your file. Things make more sense to me now⌠I see youâre not using repeaters for your lists, which is fineâjust a bit more work to setup and maintain, and more work if you need to go from say, 5 cards to 20 and donât have repeaters. On the other hand, repeaters are fairly complicated and take a good while to get used to using them.
Your counter dynamic panel (dp) has 6 states but there are only 5 states/cards in the carousel dp. That is why they match for the first 5 ânextâ state changes, then donât match thereafterâbecause you are showing card State1 but the counter is at State6. The easiest fix is to just delete State6 from âkaarenset1_nummerâ dp, but there are more reliable methods to ensure two dpâs have matching states. Also, if you only need to change the text value (e.g., from â1/20â to â2/20â) then you can just use Set Text and have one counter widget instead of a different one (as a dp state) for every card. I demonstrate methods for each below.
Here is an update to your file with examples of how I would approach this, first without, then with repeaters.
Testcardcarousel_addtofavourites.rp (586.6 KB)
I duplicated your two pages into a folder named, âVersion 2â and applied much of what I recommended in my previous demo file, but kept your basic structure. Some key detailsâŚ
- (I moved the hidden widgets to the back so it was easier to edit things on the canvas. I added the âbring to frontâ option to the Show actions for these âmore infoâ card widgets.)
- For the sake of demonstration, I made five global variables, one for each card (Fav1, Fav2, etc.) that keep track of favorites (liked cards.)
- I made a selected interaction style for the heart icon-button, along with click and selection state events to like and unlike cards. Basically all it does is set the corresponding global variableâso liking Card 1 sets Fav1 to âtrueâ.
- It is more efficient and reliable to put the Swiped actions on the parent dynamic panel (named "cardset_panel") instead of duplicating for each card within this panel. Less editing when you make changes and less chance of bugs and inconsistencies. Doing this for your 5-state carousel removed 8 events and 16 actions, simplifying your overhead.
- To ensure the counter dp state matches the card list state, I made some conditional cases in the cardsset_panel dpâs Panel State Changed event. Pretty straightforwardâit tests the state of âThisâ (cardsset_panel) and sets the state of kaartenset1_nummer to the same state. So, regardless of how the card state changes (swipe, arrow-button click, or anything else) the counter state will match.
- On the âLiked cards (2)â page, I made a âfavorite cardâ for all 5 possible cards. I kept them shown for easier editing, positioning the layout, etc.
- The Page Loaded event tests the value of each of the âFav#â global variables. If one is not âtrueâ (not a favorited card) it hides the corresponding card.
- To preserve the layout positioning of the card list, I use the âpull widgets belowâ option in the Hide actions. For example, when âQ1â (card 1) is hidden, all the widgets below itâthe rest of the cardsâare pulled up (by the height of the Q1 widget.) To make this work with the 15px gutter spacing you have between the cards, I included a 15px tall hotspot widget (just something transparent so user doesnât see it) below the widgets in each card dp (Q1, Q2, etc.) In this way, the spacing is built in to each card widget âŚif it wasnât you get all sorts of potential problems with the âpush/pullâ option.
- (I made a set of test buttons to the right which helped me debug this stuff. You can show/hide each of the cards.)
- I added a Home button in the top nav to show what happens if you delete a saved card. That card is no longer liked on the first page. See the Page Loaded event to inspect how this works.
In the Version 3 folder, I made some more simplifications, primarily to show how you can use one simple widget for the counter.
- I simplified the card list movement so everything is controlled in one place: the arrow buttons. When the cardsset_panel dp is swiped, it triggers the corresponding arrow-button click event (by using the Fire Event action to target that buttonâs Click or Tap event.)
- I could have kept the same Panel State Changed method with a case for each state to set the counter text. However, if you need to add more cards, youâd have to add more cases, and that can lead to unintended and hard-to-find bugs later on. So, the counter text is incremented in one placeâthe right-arrow buttonâusing some string and number functions to format it. Likewise, the counter is decremented in the left-arrow button.
- Because your counter string is in the format, âi/nâ and you only need to change the first âiâ number, that number needs to be extracted from the counter string, incremented or decremented, then inserted in the string again.
- Additionally, you need upper and lower limits, so it doesnât go higher than the number of cards (5) nor lower than 1. For this kind of âlooped countingâ, programmers use a modulus operator (represented with the percent-sign, as in â1%5â, read as âone modulus 5â which equates to 1. 6%5 = 1, and 7%5 = 2, etc. so the counting loopsâor is constrainedâbetween 1 and 5.)
- The magic expression in Axure for all this is:
[[ i.substring(0, i.indexOf(â/â)) % i.substring(i.indexOf(â/â)+1) + 1 ]][[ i.substring(i.indexOf(â/â)) ]]
âŚwhere i is a local variable pointing to âtext on widgetâ Target, meaning the text of the target widget of the Set Text action, which is the âkaartenset1_nummerâ widget.
âŚAlso, note that the modulus operator doesnât work with negative numbers, so I added a case for the left-arrow to adjust things when it is clicked at the first cardâmeaning it should show the last card in the list, card 5. So instead of the resulting math going to 0%5 then -1%5, it is set to 5%5 (which equates to 5.)
- Another method that could work just as well without all the string manipulation would be to use three widgets for your counter string, so instead of one widget with âi/nâ you could have:
- âcurrent_cardâ (just the âiâ) with right-aligned text
- a widget with only the slash character, â/â and center-aligned text)
- âtotal_cardsâ (just the ânâ) with left-aligned text
- Then you could use something like,
Set Text
current_card to [[ (Target.text + 1) % N ]]
âŚwhere N is a local variable pointing to the text on total_cards.
In the Version 4 folder, I use a repeater for the card list.
- So, there is only one âcard elementâ that gets repeated N timesâ5 in this example. If you click into the repeater, named âQuestions Listâ, and look at the Style pane, youâll see the datasheet used to control the content and state of the repeater cellsâeach card in the list. I pasted in some questions from a 20-questions game list. I also made a âMoreâ column for content to be displayed in the More Info card.
- To add a sixthâor any number of moreâcard, just add a row to the datasheet and type or paste info.
- The repeater automatically sets the ânâ value in the counter, by testing for the last row in the datasheet, and updating the counter total based on how many card items (rows) there are.
- The card changing logic is a different with the repeater, because the repeater becomes an actual list of cards, instead of dp states, it needs to be moved left-right so the current card is shown in the middle of the screen (so actually more like a true carousel.) Axure doesnât allow repeaters to be moved, so I created a dynamic panel from the repeater, named âQuestions Panelâ and move that instead.
- When the right-arrow button is clicked, the counter is incremented and the next card is shown by moving Questions Panel to the left by the width of one card plus the margin between cards.
- I use some hotspots the same size as the left and right margins to help with calculating exactly how much to move Questions Panel.
- I also set a right-most boundary on this movement, so the list doesnât keep moving to the left way off screen.
- I include a conditional case to test when the last card is shown (the Questions Panel dp has moved as far left as it can go) and if so, âwrapâ the list by positioning it at the starting point (as far right as the list can go.)
- Because the repeater can contain any number of cards, I use a single global variable, named âvarFavoritesâ to store an array of liked cards, rather than a bunch of separate global variables, one per card.
- Just as in my previous demo file, selecting the heart on a card adds its card number to the global variable, and unselecting it removes its number from the variable
- On the âLiked cards (4)â page, there is a repeater for the saved cards. It is styled to show the list vertically, and the visuals for the cards are different, but the repeaterâs datasheet is largely the same. I just copy-pasted the first two columns from the repeater on the first page.
- As in my previous demo, a âShowFavsâ dp is used as a recursive subroutine to walk through the array of liked cards and show the correct rows by updating their âFavâ column values if true, then applying a filter to show only the rows where âFavâ equals true.
- When a saved card is deleted (user clicks the trashcan icon) the varFavorites array is updated to remove its card number, then its own âFavâ column value is set to âfalseâ. Because the repeater is already filtered to only show rows where âFavâ is true, this row is thus automatically hidden. The repeater also automatically updates is layout so there is no extra gap in the cards. Nice!
Version 5 is the same as Version 4. I just pasted more card rows into the repeater to show how easy and quick you can go from a few cards to many cards, once the repeater âinfrastructureâ is set up.
Thank you so much! I do have another small question. I have replaced the menu on top with a menubar below (in your version 3). How can i make sure that the menu barâs position is always fixed on the liked cards page? Now it moves along with the adding and deleting of cards.
I would recommend putting the list of cards in a dynamic panel (and/or pulling the menu bar out of the dynamic panel, depending on which version of mine youâre using, and decrease the height of the dynamic panel accordingly), and placing the menu below and in front of the dynamic panel. The âpush/pullâ actions will be limited to widgets inside the same dynamic panel, so wonât affect widgets outside of the panel.
Thank you so much. I have one final question about the add to favourites function. I now have two card sets in my app and it looks like the add to favourites only works when going from the first set directly to the favourites page or from the home page to favourites, but when i first go to the home page with the back button in the first card set page, it doesnât seem to work anymore. Also when i have already liked cards from the first set, the cards i like from the second set donât appear in the favourites page. It seems as though pressing the back button completely removes liked cards. I will send you a link so you can see that it doesnât work properly. I canât figure out why. I hope you can help me.
addtofavourites_testfinalquestion.rp (592.8 KB)
Hmm⌠I see what you mean. What you coded should work, but doesnât. This looks like a bug in Axure, in which navigating back to the previous page resets all the global variables to their default values. Iâm not sure why, but the same thing happens if you click the browserâs Back button. It looks like the browser and/or Axure treats the two pages as separate webspaces or somethingâŚ
This gets more strange though⌠In testing your prototype, I noticed that if you never use the Back button, and only use the Home and Heart buttons in the bottom navâwhich link directly to named pages in the prototypeâthen global variables donât get reset, and everything performs as expected. If you then use the Back button after having navigated to Home by pressing the Home button in the bottom menu, the global variables donât get reset! âŚUntil you go to the âSet2â page, like a card, and click the Back button. Then, any global variables set from that âSet2â page get reset, but not global variables set from the âSet1â page. So, this results in a somewhat predictable but mostly illogical behavior. Weâre going to need help from Axure Support. I want to test this a bit further and then submit a bug to support@axure.com and see what they say.
In the meantime, here is a somewhat convoluted workaround. It shouldnât be necessary, but I canât think of a better way to do this, given Axureâs strange global variable behavior. In this updated .rp file, I changed the behavior of the Back buttons on your pages so they only use direct links to pages in the prototype. To do this, I used the (otherwise unused) OnLoadVariable to keep track of the âpreviousâ page.
addtofavourites_testfinalquestion_solved.rp (597.9 KB)
-
On the âHomeâ page there is no Back button, so its Page Loaded event just sets OnLoadVariable to its name, using the built-in variable, [[PageName]]. Thus, OnLoadVariable = âHomeâ.
-
On each of the other pagesâall pages in the âConvo cardsâ folderâI only set OnLoadVariable in events that link to another page, just prior to the Open Link action, in order to keep track of the page it came from. For example, the Heart button has this:
-
On each of the other pages, the Click or Tap for the Back button tests the value of OnLoadVariable. If it equals âHomeâ it opens the Home page.
- I added cases to test for the name of all the other pages. For example, the Back button (hotspot) on âSet1â page has this:
- I added cases to test for the name of all the other pages. For example, the Back button (hotspot) on âSet1â page has this:
This solution worksâat least for meâbut is pretty tedious, and if you later add more pages then you need to edit the code for all the Back buttons all over again. You could help this a bit by making a Component from the Back button so the same widget is reused on each page. Then youâd have only one set of interaction code to edit.
A drawback to this approach is you only get one level of âBackâ, so if you navigate from Home to Set1, then to Favorites, then click Back, youâd go to Set1, but if you click Back on that page, youâd return to Favorites, not Home. Keeping a running page list as a breadcrumb element is even more convoluted to keep track of in Axure. Conceivably, it should be possible to keep a running list of visited pages in one global variable and remove one page name at a time as you back up through the page list.
Here is a simplified example to demonstrate the âBackâ issue with global variables. There is only one global variable in this prototype, âOnLoadVariableâ, to eliminate any possible issues with having too many global variables, or anything else going on with some other issue in the prototype @nohorse first posted.
Global Variables and Back Link.rp (68.6 KB)
- Page 1 has a text field and a âGo To Page 2â button.
- The text field, âinputâ has Hint text (âEnter anything hereâ) and a Loaded event that sets its own text to the value of OnLoadVariable.
- The âgo buttonâ sets OnLoadVarible to the text value of âinputâ, then opens Page 2.
- It also has some test buttons to set and get the value of OnLoadVariable.
- Page 2 has a rectangle widget, âcopied textâ, a âBackâ button, and a âgo buttonâ.
- The âcopied textâ widget has a Loaded event that sets its own text to the value of OnLoadVariable.
- The âBackâ button opens a link with the âBack to previous pageâ option.
- The âGo To Page 1â button opens a link to âPage 1â directly as a page in the prototype.
Try this procedure when previewing the prototype:
- On Page 1, enter some text in the text field.
- Click the âGo To Page 2â button.
- View the result on Page 2. The âcopied textâ rectangle contains the text entered on Page 1 as expected.
- Click the âBackâ button.
- View the result on Page 1. The text field has a blank text value, and its hint has changed to the (previous) value of OnLoadVariable, which is definitely not expected!
- View the console. The value shown for OnLoadVariable is blank. However, when this page loaded, the previous value of OnLoadVariable must have been accessible because the hint text matches it.
- Click the âGet OnLoadVariableâ button. The âoutputâ widgetâs text value is blank, confirming OnLoadVariable is blank.
- Click the âGo To Page 2â button.
- View the result on Page 2. The âcopied textâ rectangle text value is blank, confirming OnLoadVariable is blank.
Now reload the prototype and try this procedure when previewing the prototype:
- On Page 1, enter this text: âoneâ, in the text field.
- Click the âGo To Page 2â button.
- View the result on Page 2. The âcopied textâ rectangle contains âoneâ, as expected.
- Click the âGo To Page 1â button.
- View the result on Page 1. The text field contains âoneâ, as expected.
- Enter âtwoâ in the text field.
- Click the âGo To Page 2â button.
- View the result on Page 2. The âcopied textâ rectangle contains âtwoâ as expected.
- Click the âBackâ button.
- View the result on Page 1. The text field contains âtwoâ, as it should.
- So why does this work only when the âBackâ button is clicked after previously clicking the âGo to Page 1â button?
- Now view the console to inspect the value of OnLoadVariable. It is âoneâ, which is not expected!
- This is even more problematic as the resulting behavior after clicking the âBackâ button changes, and furthermore, the value of OnLoadVariable has changed from the time the page loaded to the time it can be accessed programmatically.
Now reload the prototype and try this procedure when previewing the prototype. Keep the console open and observe the value of OnLoadVariable as you proceed.
- On Page 1, enter âoneâ in the text field.
- Click the âGo To Page 2â button and view the result.
- Click the âGo To Page 1â button and view the result.
- On Page 1, enter âtwoâ in the text field.
- Click the âGo To Page 2â button and view the result.
- Click the âBackâ button and view the result.
- Repeat steps 4â6 several times, entering âthreeâ, then âfourâ then âfiveâ.
It appears to work correctly each time, but note that OnLoadVariable is set to âoneâ every time Page 1 loads, but only after the Loaded event is processed. Of course this is very confusing and problematic. By the way, I also tested this using the Page Loaded event to set the text fieldâs value, and got the same results.
I published this prototype to Axure Cloud here: https://74pc1w.axshare.com
- Same crazy results. You can verify the value of OnLoadVariable by clicking âGet OnLoadVariableâ on Page 1 at any time.
I also tested this out on RP 9 and got the same results. Could not find any previous posts with this exact problem, but it looks like it may have been around since at least RP 8.
We were able to do some testing with the âOpen Link > Backâ interaction and found that this is indeed unexpected that the global variables get reset. Iâve gone ahead and filed this as a bug for our engineers to investigate.
Thanks for discovering this bug for us! Hopefully, using @mbc66âs solution to use direct links and different cases to specify which page to go to works in the meantime.