So, I continue to describe my experience creating an application for vkontakte.ru. In the
first part, I described how the initial version of
my music player was created . In this part I will describe how I added the server part.
The application is done using Flex, and under the cut my experience with such things is described: TabNavigator, Menu Control, working with coordinates, pop up windows, TitleWindow, homemade events, Zend, Zend AMF, working with a database, ItemRenderer, crossdomain policy.
In short, I simply describe how I learned to add, read, update and delete information from the database using the Flex + Zend AMF bundle.
')
06/10/09
So, in the first 5 days, "Media" gathered 330 users. I do not know whether it is a lot or a little, but the main thing is that the process has begun and is not going to stop. During these five days I gave myself a break and practically did not work on the player, but I cannot relax, so I resume work and, as before, I will document my actions.
For the first few days, a decent amount of bugs and suggestions from users were gathered, which I will gradually eliminate / add. Today I started by adding the lastFM logo and links to
www.lastfm.com , this is a mandatory requirement when using their API.
One user suggested adding the ability to play all the music of friends in one list. In my opinion, sound thought, so we will deal with it. Unfortunately, it is not possible to transfer the list of users to the contact's API and get a list of their audio recordings in one request, which means you have to consistently transfer user IDs to the API and add the received records to the general list. Another interesting question is how to add the “All friends music” field to the ComboBox (drop-down list of friends), this field is out of the general structure and must be processed in a separate way. Let's start with this.
Adding a new item to the ComboBox was elementary, creating a new XML like so var newNode: XML =, and then adding it to the desired XMLListCollection via .addItemAt (newNode, 1). Unfortunately, thinking about further actions, I decided that the function of listening to all the music of friends in one list so far does not need to be done. In order to compile a complete list of music, for example, with 45 friends, you will need to send 45 requests to the contact's API, with the maximum you can send 3 requests per second, hence it will take at least 15 seconds to compile the list, and if there are any then mistakes, the result I can not even predict at all. In general, we will leave this function for the future, either I will wait until it becomes possible for the contact to transfer several uids with the getAudios method at once, or I will do the necessary function through my own server and own database.
13.06.09
Today to the country, but as long as there is time, I will continue to develop. Let's start by fixing the Play button. Judging by the reviews, when you click on Play, people expect that the selected track will start playing (which is, in general, logical, but initially I was guided by WinAMP in this question). The task was solved quickly and there was nothing special to do, just a few if ... else constructs.
I was also asked to press the “Next track” button, if the list is over, the very first track should start. Well, this is absolutely elementary, even nothing to describe.
Now change the volume so that the volume changes when the half-bone moves, and not just after releasing it. Elementary again, just adding liveDragging = "true" to <mx: HSlider>.
When the random track playback function is enabled, the Next track button should go to the next random track, not the next one in the list. Again, nothing complicated, if else and everything.
<Once>
Perhaps I will no longer indicate specific dates, because the time intervals between them are depressing. In a previous post, I somehow did not write what I did to count down the time (until the end of the track). In general, I did it and no longer remember how. In addition, I finally made a less noticeable improvement. Now, not only information about the performer is displayed, but also the text of the current song, which is taken from lyricwiki (I spied the idea with the text in another player, which I honestly confess to). To display the lyrics, I made a right column using the viewstack component, which allows you to switch the displayed information. In the first layer of the viewstack, I left the information about the artist, and in the second layer I placed the self-made component of the lyrics, which displays the text (everything is elementary, just sending a request to the lyricwiki API using HTTPService and outputting the result). Switching between layers (I call it layers, although perhaps there is some more appropriate definition) I made using the Combobox component, again, everything is simple, at the level of the property change = "vsRightColumn.Selectedindex = cbShow.Selectedindex". Everything worked the first time, the only problem is that before the first switching of the viewstack to the display of the lyrics, the lyrics component is not active. As a result, when you first switch to the song text, no text is displayed, but starts to be displayed, either after the second switch, or after changing the track, but I think this can be solved with the help of the init () function and, possibly, a self-made event, which will send the message that the component is initialized.
<Once>
These <sometime> headers are probably not the best idea. Now I will come up with headlines with meaning (or without meaning, but at least interesting). So, it seems, I finally matured for the first experiments to add interaction with the database and PHP. The first function that I would like to implement using the database will be the ability to recommend the track to my friends (of course, only to those who installed the application). Of course, for these purposes it is already possible to use the “wall”, but through the player it should be more convenient, and besides, there is hope that this will stimulate users (of whom there are only 750) to invite their friends to the app.
So the first thing I need is to design a database. I am in this case a complete zero, so I climbed for information on Habr. To begin with, these two articles were enough for me (thanks to the authors):
http://abarmot.habrahabr.ru/blog/23423/ ,
http://habrahabr.ru/blogs/development/45707/ . After reading the articles, I singled out for myself 3 entities: the user, the track, the recommendation, and made the corresponding tables for them (users, audio, advices), which, it seems to me, satisfy the normal forms. I also recently read a small part of the Getting Real book from 37signals and it was written there that you first need to make an interface. Once, for the sake of interest, I wanted to use their products, I poked 5 seconds into links on their website, did not understand anything and scored, but still, they, apparently, are tough guys and know what (I like the letter Y) , so follow their advice, run Flex Builder and make an interface. With the interface, by the way, the problem is, because I already run out of free space for all buttons, etc., but I will not pay attention to this in the best traditions of the redneck designer and I will sculpt as it will. Pretty soon, this approach will cease to work, and then I will have to thoroughly rework everything, but at least I will already know what I can design and draw on from the real experience of a very specific application. In general, I will cut seven times, in order to understand how best to measure.
Recommendations can be immediately played in the player, so I will display them in place of the list of songs. I will switch between recommendations and my list with the help of Viewstack and buttons on the player panel. I'll start with the buttons. No, I tried to start with the buttons and it turned out badly. I'll start with Viewstack. So, I need to add a viewstack and place a good old list of songs on one layer, and a datagrid with recommendations on another layer. No, I lied again. It turns out that TabNavigator is best suited for my purpose - this is the viewstack to which tabs are already attached, to switch between layers. Yes, now everything is correct. TabNavigator is just super, it's even strange why I didn't use it before. Now you need to add a TabNavigator datagrid (or, quite simply, a table) to display recommendations. The datagrid should consist of the following columns: the sender of the recommendation, the name of the artist and the track, the duration and the button for receiving an additional comment if the sender left it. It turns out quite a lot and in one line it will all look bad, you need to find a way to make the sender above the track. It is probably better to use TileList. In general, I have so far put an empty TileList, then it will be necessary to further refine it when a working dataProvider appears. We assume that I have an interface for displaying recommendations, now I need an interface for sending recommendations. Here, it seems the time has come to add a new column to the DataGrid with a list of songs. This column will contain a button to open the track menu. For now, the menu will have only one function - “recommend”, but over time, others may appear.
<I continue to make recommendations>
First, I decided that it would be logical to make the track menu using Menu control. First, I added a new column to the DataGrid with tracks, in this column, with the help of itemRenderer put the usual button. The first thing I need to do is display the menu in the right place, i.e. close to the upper right corner of the pressed button, for this I need to determine the coordinates of the button. The X coordinate will not change, just like the button size, so the task is reduced to determining the Y coordinate. To check how it works, I added the click = "testButton (event)" button and, in fact, the function itself:
public function testButton (e: Event): void
{
var tmpString: String = new String ();
tmpString = "y:" + String (e.target.y);
Alert.show (tmpString);
}
As it turned out, to call a function from itemRenderer, you must use the outerDocument property, and the function itself should not be private. Thus, the click property had to be rewritten with click = “outerDocument.testButton (event)”, after that everything worked and I got the Y coordinate. The y coordinate was shown relative to the parent element, it was not what I needed, I need to know the coordinate relative to the entire component. Go to google and write
flex 3 coordinates, the first link is the article “Using Flex coordinates” from the Adobe Flex 3 Help. From the article it becomes clear that there are three types of coordinates: global, local and content, I need exactly the global coordinate and you can get it using the localToGlobal method. Let's try to use this design.
public function testButton (e: Event): void
{
var tmpString: String = new String ();
var pt: Point = new Point (e.target.x, e.target.y);
pt = e.target.localToGlobal (pt);
tmpString = "y:" + String (pt.y);
Alert.show (tmpString);
}
As expected, we got the global Y coordinate. Now you need to create a menu. As always, we turn to Help'u, in this case to "Using Menu-Based Controls". First you need to define the menu structure, it can be done in various ways, I personally will do it through XML, like this:
<mx: XML format = "e4x" id = "trackMenu">
</ mx: XML>
Now we make a function to display the menu:
public function showTrackMenu (e: Event): void
{
var trackMenu: Menu = Menu.createMenu (null, trackMenuData, false);
trackMenu.labelField = "@ label";
var pt: Point = new Point (e.target.x, e.target.y);
pt = e.target.localToGlobal (pt);
trackMenu.show (pt.x + e.target.width, pt.y);
}
Assign this function to a button: click = "showTrackMenu (event)". The result was unexpected, the horizontal menu is excellent, but the Y menu goes down exactly as many as the buttons above are pressed. I could not understand why this happens, so I did this:
trackMenu.show (e.target.x + e.target.width + 3, e.target.y + 294);
The solution, of course, sucks, but it works - the menu appears where necessary. Now it is necessary that when you click on a menu item, a pop-up window appears with a list of friends and the ability to enter a comment on your recommendation. First you need to understand how, in general, to determine that the user has clicked on a specific menu item, for this I will add another “close” item to the menu and I will experiment with it. Everything turned out, it is very easy to add eventListener and a function to handle the event. For the sake of experiment, I added the selectedMenu = "rec" property to the first menu item, and then checked how it works:
public function showTrackMenu (e: Event): void
{
var trackMenu: Menu = Menu.createMenu (dgTracklist, trackMenuData, false);
trackMenu.labelField = "@ label";
trackMenu.addEventListener (MenuEvent.ITEM_CLICK, itemClickInfo);
trackMenu.show (e.target.x + e.target.width + 3, e.target.y + 294);
}
private function itemClickInfo (event: MenuEvent): void
{
if (event.item. @ menuSelected == "rec") Alert.show (event.item. @ menuSelected);
}
Everything worked fine. Now you need to add a pop-up window, with a list of friends, a field for comment and the buttons "send" and "cancel." I already know that you need to use TitleWindow for this. Despite the fact that the “recommend track” menu is in the player component, I will create a pop-up window from the main application, which means you need to transfer the event from the component with the player to the main application. At the same time, in event'e it will be necessary to transmit full information about the recommended track. We need to create a custom event. It seems that I have not yet described the creation of event events, so I will describe this process in more detail. First I added the definition of a new event.
<mx: Metadata> [Event (name = "giveAdvice", type = "com.vkapps.events.GiveAdviceEvent")] </ mx: Metadata>
Now you need to create it, create a new file GiveAdviceEvent.as and describe the event itself in it. Here I will post the source directly, I think everything is clear in it:
package com.vkapps.events
{
import flash.events.Event;
public class GiveAdviceEvent extends Event
{
public static const GIVE_ADVICE: String = "giveAdvice";
public var aid: String;
public var owner_id: String;
public var artist: String;
public var title: String;
public var duration: String;
public var url: String;
public function GiveAdviceEvent (aid: String, owner_id: String, artist: String, title: String, duration: String, url: String)
{
super (GIVE_ADVICE);
this.aid = artist;
this.owner_id = title;
this.artist = artist;
this.title = title;
this.duration = artist;
this.url = title;
}
override public function clone (): Event
{
return new GiveAdviceEvent (this.aid, this.owner_id, this.artist, this.title, this.duration, this.url);
}
}
}
Well, there is an event. It remains only to send it at the right time, like this:
private function itemClickInfo (event: MenuEvent): void
{
if (event.item. @ menuSelected == "rec")
{
var giveAdvice: GiveAdviceEvent = new GiveAdviceEvent (dgTracklist.selectedItem.aid, dgTracklist.selectedItem.owner_id, dgTracklist.seidItem.artist, dgTracklist.septItem.title, dgTracklist.selectedItem.duration, dgTrack.ta..title, dgTracklist.selectedItem.duration, dgTrack.ta.ti.title, dgTracklist.selectedItem.duration, dgTrack.ta.ti.title, dgTracklist.tistle, dgTracklist.tistle, dgTracklist.tistle, dgTracklist.title, dgTracklist.
dispatchEvent (giveAdvice);
}
}
Go to the main application and add event listener:
player.addEventListener (“giveAdvice”, giveAdvice);
Now you need to prepare a pop-up window, for creating a new component based on TitleWindow. Place the necessary controls there and declare variables that will contain information about the track, approximately, like this:
[Bindable]
public var _artist: String;
Again we return to the main function and write a function to handle the event (I have reduced it):
private function giveAdvice (event: Object): void
{
var pop1: AdviceForm = AdviceForm (PopUpManager.createPopUp (this, AdviceForm, true));
pop1.title = "Submit a recommendation";
pop1.showCloseButton = true;
pop1._artist = event.artist;
...
pop1._url = event.url;
PopUpManager.centerPopUp (pop1);
}
Everything, the main interface for recommendations is ready. Now we need the server part.
<API, Zend and all such things>
I wrote that the interface for recommendations is ready, but in fact I lied a little. For complete happiness, it is necessary that the pop-up TitleWindow displays a list of friends who installed the application, because you can only recommend them. In the api contact there is already a suitable getAppFriends method. The problem is different - to create a request to api right in the pop-up window again - this is completely ugly and leads me to the need to make a separate class for working with contact api and then send all requests using instances of this class (actually, this is obvious, but before the urgent need for this was not). Making the class was elementary, difficulties arose when I needed to return the result of the query from the class to the component. The only (at least others I don’t know) way to do this is in the event. I need to send the ResultEvent to the top, for this I again made my own event:
import flash.events.Event;
import mx.rpc.events.ResultEvent;
public class CustomResultEvent extends Event
{
public static const FRIENDS_RESULT: String = "friendsResult";
public static const FRIENDS_APP_RESULT: String = "friendsAppResult";
public var customResult: ResultEvent;
public function CustomResultEvent (type: String, e: ResultEvent)
{
super (type);
this.customResult = e;
}
override public function clone (): Event
{
return new CustomResultEvent (type, customResult);
}
}
In the file with the API, I send it, for example, as follows:
private function friendsAppRequestHandler (event: ResultEvent): void
{
var friendsAppResult: CustomResultEvent = new CustomResultEvent ("friendsAppResult", event)
dispatchEvent (friendsAppResult);
}
And I accept, for example, like this:
private function init ()
{
api.addEventListener ("friendsAppResult", friendsHandler);
api.requestAppFriends (this._uid);
}
[Bindable]
private var lFriendsList: XMLListCollection = new XMLListCollection ();
private function friendsHandler (event: CustomResultEvent): void
{
lFriendsList.source = new XMLList (event.customResult.result.user);
}
Now the interface is actually ready and again now you need to do the server part. So far it seems difficult. For the server part, I plan to use Zend, so go google looking for a “flex zend”. The first link leads to the article: “Flex and PHP: Party in the Front, Business in the Back”, I quickly ran through her eyes, but it seems that this is not what I need. After that, I switched to the article “Integrating Adobe Flex and PHP” and this is already something closer to what I was looking for (despite the fact that Zend is not used in it). At the end of the article there was a link to the author's blog:
blogs.adobe.com/mikepotter I
followed the link and there, for the first time, read about AMF. Further searches in this direction convinced me that the use of the Flex + Zend AMF bundle would be optimal and in this sense two articles helped me:
"Using Zend_Amf and Adobe Flex SDK" (
http://zendframework.ru/articles/flex-with-zend-amf )
"Flex and PHP: remoting with Zend AMF" (
http://corlan.org/2008/11/13/flex-and-php-remoting-with-zend-amf/ )
<Denver, Zend, AMF>
In general, the basic things, I think I understood. We must proceed to practice. I will debug on my own computer, which means I need to run Denwer and configure everything there, including, of course, the database. In the local database, I created two tables: audio and advice. The audio table structure repeats the xml response from the contact, and the advice table now consists of the following fields:
id - recommendation number, aid - song id, owner_id - song owner id, sender_id - id of the person who sent the recommendation, comment - comment on the recommendation, read - indication whether the recommendation is read or not.Now you need to configure the Zend part. First, the whole framework is hardly useful to me, so I limited myself to the components: Amf, Db, Filter, Validate, Xmlrpc. Let's try to create Amf endpoint, as described in the article. For writing PHP code, I will use NetBeans, I tried to use Zend Studio, but my whole business slowed down so wildly that I had to abandon this idea. I’ll start by creating Value Object (descriptive class) for recommendations: VOadvice.php. I will not describe the creation process, everything is already described in the article I mentioned earlier. In general, having done everything that is written in the article about the creation of the server part, I got the desired line in the Zend Amf Endpoint browser, i.e. in theory, everything works. Go back to Flex and add interaction. At first I created the services-config.xml file, there was a small snag here,it turned out that there should be spaces at the beginning of the file. Fortunately, google immediately enlightened me about this. The rest of the day I spent in desperate attempts to understand why nothing works. More precisely, why the code from the article works, but my completely analogous code does not. I did not find the exact reason (perhaps the reason was that in my recommendation model, I did not describe the id field), in the end I just meticulously changed the names of the variables in the code from the article and I managed to add a recommendation. This is definitely wine, otherwise I was already afraid that I would have to go to bed to the vanquished, with gloomy thoughts about an unexpected deadlock. Fortunately, everything was decided, which means that tomorrow I will be able to continue to move towards the goal.The rest of the day I spent in desperate attempts to understand why nothing works. More precisely, why the code from the article works, but my completely analogous code does not. I did not find the exact reason (perhaps the reason was that in my recommendation model, I did not describe the id field), in the end I just meticulously changed the names of the variables in the code from the article and I managed to add a recommendation. This is definitely wine, otherwise I was already afraid that I would have to go to bed to the vanquished, with gloomy thoughts about an unexpected deadlock. Fortunately, everything was decided, which means that tomorrow I will be able to continue to move towards the goal.The rest of the day I spent in desperate attempts to understand why nothing works. More precisely, why the code from the article works, but my completely analogous code does not. I did not find the exact reason (perhaps the reason was that in my recommendation model, I did not describe the id field), in the end I just meticulously changed the names of the variables in the code from the article and I managed to add a recommendation. This is definitely wine, otherwise I was already afraid that I would have to go to bed to the vanquished, with gloomy thoughts about an unexpected deadlock. Fortunately, everything was decided, which means that tomorrow I will be able to continue to move towards the goal.in the end, I just meticulously changed the names of the variables in the code from the article and I managed to add a recommendation. This is definitely wine, otherwise I was already afraid that I would have to go to bed to the vanquished, with gloomy thoughts about an unexpected deadlock. Fortunately, everything was decided, which means that tomorrow I will be able to continue to move towards the goal.in the end, I just meticulously changed the names of the variables in the code from the article and I managed to add a recommendation. This is definitely wine, otherwise I was already afraid that I would have to go to bed to the vanquished, with gloomy thoughts about an unexpected deadlock. Fortunately, everything was decided, which means that tomorrow I will be able to continue to move towards the goal.<AMF, PHP, >
I have a lot of other things to do, so today I’d hardly be able to work a lot on the player, but I’ll select a couple of hours. So, yesterday I stopped at the fact that I could force the code from the example to add a new recommendation to my database, now I need to bring this code into line with my project, i.e. in fact, I need to carefully rename the classes and methods so that they reflect my purpose, and not the purpose from the article (in particular, I need to rename everything that was User and Users into Advice and Advices). Renaming was successful, recommendations are added (although sometimes there are errors similar to the timeout, this is confirmed by entries like “Maximum execution time of 30 seconds exceeded” in the logs, but for now I will not focus on them). Now you need to add various checks for data correctness and, first of all, you need to make surethat the request was sent by the Contact user and exactly to the one specified, for that there is a contact provided by auth_key. It's time to use it. auth_key is calculated on the VK server as follows:auth_key = md5 (api_id + '_' + viewer_id + '_' + api_secret) and transferred to the application via flashvars, api_secret is known only to the author of the application, therefore in the server part, I will have to calculate md5 and compare it with the data sent from the application so that make sure that the request is sent by the correct user. At first, I had to at least just have a look at this very auth_key, in Flex it is stored in Application.application.parameters.auth_key, for a long time I could not get this key until I found out that in the settings of the VKontakte application, I had to go to The “payments” tab and these same payments are included, only then does the auth_key start to be issued. In general, in Flex I received the key, now I need to transfer it to the server. Therefore, we will now send 2 parameters to the server: an object with a recommendation and an auth_key for comparison.On the server, re-create the md5 with api_secret known to us and passed to viewer_id, if the keys match, then everything is fine. Nevertheless, theoretically, an unscrupulous user can still recognize his own auth_key and then send requests to bypass my Flex shell, so the transferred data should be checked anyway. Well, apparently Zend Filter and Zend Validate help me. I go to read docks on these components. After reading some simple documentation, I added a few filters and validators that properly filter incoming data. So, at the moment we have the opportunity to add a recommendation and it is hoped that it will be added in a safe manner. We need another check - check for the existence of such a record on VKontakte, i.e.we must send a request to the contact and get an answer in which the field of aid received from the client and received from the contact API will match. In case of successful verification, if a song with this aid does not exist in the audio table, you need to add it there. After that, it will be necessary to receive a list of recommendations for a specific user from the server, display recommendations in a convenient form, delete recommendations and send notifications to the user that he has received a new recommendation. In general, again I spent all day with the player and did nothing of the other things. So now I will return to the player only after I do the most urgent things, I think this is a good motivation to deal with them sooner.After that, it will be necessary to receive a list of recommendations for a specific user from the server, display recommendations in a convenient form, delete recommendations and send notifications to the user that he has received a new recommendation. In general, again I spent all day with the player and did nothing of the other things. So now I will return to the player only after I do the most urgent things, I think this is a good motivation to deal with them sooner.After that, it will be necessary to receive a list of recommendations for a specific user from the server, display recommendations in a convenient form, delete recommendations and send notifications to the user that he has received a new recommendation. In general, again I spent all day with the player and did nothing of the other things. So now I will return to the player only after I do the most urgent things, I think this is a good motivation to deal with them sooner.I think this is a good motivation rather to deal with them.I think this is a good motivation rather to deal with them.<, , «», >
For a few days I left the city, first with friends to the dacha, then to the tourist center, then I noticed something else in the city, in general, I rested for five days and did not work on the player. It is very good that in the previous post I described the approximate front of the next works and now I don’t have to go long. So, we need to send a request to the contact, it seems that the time has come for the xmlrpc module. Useful to understand. So, it seems xmlrpc is at all that. Most likely you need zend_rest, I'll try to read about it. I read it, tried it, but in the end I did it through the usual Zend_Http_Client (I will redo it if necessary). In response, I received “User authorization failed” from the api contact, apparently the requests from the server should be made in a more complicated way, but now it is three in the morning, so I will go to sleep, and tomorrow I will deal with this issue.< secure>
As it turned out, only secure requests can be sent from an external server to an api contact, normal requests cannot be sent. This is a rather unpleasant news, because I am deprived of the opportunity to check the received data, I think the api developers will eventually solve this problem, but for now I’ll have to be content with what we have. Well, then we’ll assume that adding recommendations works. Now we need to make a receipt of recommendations. To begin with the server part. As soon as I started doing, I immediately noticed that I forgot the most important thing - the recipient's id. Just add the appropriate field to the database and make small changes to the client and server parts, there is nothing special to describe here. After the changes, a gid field appeared in the database in which the recipient's id is entered. Now for sure, you can proceed to receive recommendations.<Database>
It was originally thought that the client would receive only id tracks, and then he himself would send a request to the contact for the rest of the information, but after a little thought, I realized that this was too inefficient. With more than twenty recommendations, their receipt will take too much time. Therefore, I will keep all the tracks on the server, as well as user data and I will receive this information from the server. To do this, I added new properties to my model of advice, including full track information and sender information (first and last name). Now when sending a recommendation, the presence of such a track in the audio table is checked and if there is no such track, it is entered into the table. We do the same with the sender, if it is not already in my database, then add it there. Accordingly, when we receive a recommendation,We get full information: recommendation, track information, information about the sender. To display the recommendations in the application, I decided to use a regular list with item renderer.<Final touches>
I worked here for a few more days and, it seems, did everything. For starters, I made an itemRenderer for a list of recommendations. itemRenderer includes a label pair with an indication of who came from the recommendations and the name of the track, TextArea, in which the comment and the delete button are displayed. The list ispopulated like this: private function getAllHandler (event: ResultEvent): void{
if (event.result.toString () == '') Alert.show ('No recommendation');else tlAdvices.dataProvider = event.result as Array;adviceLoaded = true;}
The request for a list of recommendations is sent when switching to the appropriate tab, and only once, for this is the variable adviceLoaded. All this construction worked well and I moved on to the next step. Now it was necessary to make a list of unread recommendations, and if the number of unread recommendations is more than zero, then this value should be displayed in parentheses in the header of the “recommend” tab, i.e. should be something like “recommendations (2)”. We create a new countRead method on the server, I will not describe it entirely, in general, after all the checks, such a request is sent and its result is returned:$ select -> from (array ('a' => 'advice'), array ('num' => 'count (*)'))-> where ('a.gid =?', $ gid)-> where ('a.read = 0 ');Accordingly, in the client, when you start the player, we send the roAdvices.countRead (cuid, auth); request, and process the result of the query like this:private function countReadHandler (event: ResultEvent): void{
if (int (event.result)> 0) cAdvices.label = cAdvices.label + '(' + String (event.result) + ')';}
Well, it works, now you need to somehow mark the read and unread recommendations, as well as add a mechanism that will translate the recommendation into the category of read. Here I decided to use the scheme that is implemented in “TheBat”, unread recommendations are highlighted in bold, read - normal, and the recommendation is considered to be read after clicking on it. Read or unread is determined by the read property, which can be 1 or 0, respectively. To display the status of the recommendation, naturally, it is necessary inside itemRenderer. To do this, add a few actionscript:override public function set data (value: Object): void {super.data = value;if (int (data.read) == 0){
lSender.setStyle ("fontWeight", "bold");lTrack.setStyle ("fontWeight", "bold");}
else
{
lSender.setStyle ("fontWeight", "normal");lTrack.setStyle ("fontWeight", "normal");}
if (String (data.comment) == '') taComment.htmlText = 'No Comments';if (String (data.comment)! = '') taComment.htmlText = data.comment;}
This allowed us to separate read and unread recommendations. Now it is necessary that after clicking on the recommendation, it becomes read. This is done in two stages: firstly, you need to change the read field for this recommendation in the database on the server, secondly, to display these changes in the application. Add the itemClick = “checkRead (event)” property to the List component, and then write the function itself and the event handler for it:public function checkRead (event: Event): void{
if (int (event.currentTarget.selectedItem.read) == 0){
index = event.currentTarget.dataProvider.getItemIndex (event.currentTarget.selectedItem);roAdvices.updateRead (event.currentTarget.selectedItem.id, cuid, auth);}
}
private function updateReadHandler (event: ResultEvent): void{
tlAdvices.dataProvider [index] .read = 1;tlAdvices.invalidateList ();}
Well, on the server we create the updateRead method, which ends like this:$ adviceData = array ('read' => 1);$ where [] = "id =". $ this -> _ db-> quote ($ id);$ where [] = "gid =". $ this -> _ db-> quote ($ gid);return $ this -> _ db-> update ('advice', $ adviceData, $ where);invalidateList () in the updateReadHandler function is needed in order for the changes to appear in the list.At the moment, we already have the opportunity to create, receive and update the recommendation, it remains only to add the deletion. For deletion, we have a “Delete” button in itemRenderer. It is considered a good practice to send an itemRenderer event to the top, which some function will catch there and do the necessary action, but for some reason I didn’t manage to do that, so I did all the actions to delete the entry directly from itemRenderer. Here I had to get acquainted with such a thing as listData. The listData contains information about the parent component; I need this information in order to delete the entry in the database on the server as well in the application. To begin, I added the propertyimplements = "mx.controls.listClasses.IDropInListItemRenderer" to the base component, in my case it was vbox:<? xml version = "1.0" encoding = "utf-8"?><mx: VBox xmlns: mx = " www.adobe.com/2006/mxml " width = "298" height = "124" horizontalScrollPolicy = "off "PaddingLeft =" 10 "paddingRight =" 10 "paddingTop =" 10 "dropShadowEnabled =" true "dropShadowColor =" # A4A4A4 "borderStyle =" solid "borderThickness =" 0 "implements =" mx.controls.listClasses.IDropInListItemRenderer ">A also added functions:private var _listData: BaseListData;public function get listData (): BaseListData{
return _listData;}
public function set listData (value: BaseListData): void{
_listData = value;}
Now I have access to listData, and then everything is simple. Add a couple of functions:private function itemDelete (): void{
var ow: List = listData.owner as List;index = ow.dataProvider.getItemIndex (this.data);roAdvices.deleteById (this.data.id, parentDocument.cuid, parentDocument.auth);}
private function deleteHandler (event: ResultEvent): void{
var ow: List = listData.owner as List;ow.dataProvider.removeItemAt (ow.dataProvider.getItemIndex (this.data));ow.invalidateList ();}
Well, in the button, add the property click = "itemDelete ()".In the list component, which displays a list of all recommendations, I added itemDoubleClick = "onUrlChange (event)", this property starts playing the song by double-clicking on recommendations.Now everything is ready, it remains to update the application in contact. To my surprise, after downloading the new version of the application, I could not connect to the server to get recommendations. It turned out that the problem in the crossdomain.xml file, this file should be located in the root directory of the server and allow receiving data, in my case it looks like this:<? xml version = "1.0"?>
<! DOCTYPE cross-domain-policy SYSTEM "
www.macromedia.com/xml/dtds/cross-domain-policy.dtd ">
<cross-domain-policy><allow-access-from domain = " www.company.com " secure = "false" /></ cross-domain-policy>After creating this file, everything worked. If you have any questions, money, oil or servers - write.