Error When Adding Invoice - quickbooks
I am using IPP's PHP API to create invoices. I am creating the invoice using the following code:
public function create_invoice($data) {
$this->_getClientByID($data['clientId']);
$invoice = new IPPInvoice();
$invoice->AllowIPNPayment = "false";
$invoice->AllowOnlinePayment = "false";
$invoice->AllowOnlineCreditCardPayment = "false";
$invoice->AllowOnlineACHPayment = "false";
$invoice->ApplyTaxAfterDiscount = "false";
$invoice->CustomerRef = $data['clientId'];
$invoice->BillAddr = $this->_client[0]->BillAddr;
$invoice->ShipAddr = $this->_client[0]->ShipAddr;
$invoice->DueDate = Date('Y-m-d');
$invoice->PrintStatus = "NotSet";
$invoice->EmailStatus = "EmailSent";
$invoice->BillEmail = array('id'=>'', 'address'=>$data['email'],'default'=>'','tag'=>'');
$invoice->TxnDate = Date('Y-m-d');
$invoice->CurrencyRef = "USD";
$lineItems = array();
$invoiceTotal = 0;
foreach ($data['invoice_items'] as $line) {
$lineItem = new IPPLine();
$invoiceTotal = $invoiceTotal + $line->amount;
$lineItem->Description = $line->service_name;
$lineItem->Amount = $line->amount;
$salesLineItem = new IPPSalesItemLineDetail();
$salesLineItem->ItemRef = 1;
$salesLineItem->UnitPrice = $line->amount;
$salesLineItem->Qty = 1;
$lineItem->DetailType = "SalesItemLineDetail";
$lineItem->SalesItemLineDetail = $salesLineItem;
$lineItems[] = $lineItem;
}
$lineItem = new IPPLine();
$lineItem->DetailType = "SubTotalLineDetail";
$lineItem->Amount = $invoiceTotal;
$lineItems[] = $lineItem;
$invoice->Line = $lineItems;
$invoice->TotalAmt = $invoiceTotal;
$invoice->Balance = $invoiceTotal;
$result = $this->_dataService->Add($invoice);
}
Which, for my example produces the following data:
{
Deposit:"",
AllowIPNPayment:"false",
AllowOnlinePayment:"false",
AllowOnlineCreditCardPayment:"false",
AllowOnlineACHPayment:"false",
EInvoiceStatus:"",
ECloudStatusTimeStamp:"",
InvoiceEx:"",
AutoDocNumber:"",
CustomerRef:"889",
CustomerMemo:"",
BillAddr:{
Id:"5438",
Line1:"2021E Pulasky Highway",
Line2:"",
Line3:"",
Line4:"",
Line5:"",
City:"Havre de Grace",
Country:"US",
CountryCode:"",
CountrySubDivisionCode:"MD",
PostalCode:"21078",
PostalCodeSuffix:"",
Lat:"",
Long:"",
Tag:"",
Note:""
},
ShipAddr:{
Id:"5438",
Line1:"2021E Pulasky Highway",
Line2:"",
Line3:"",
Line4:"",
Line5:"",
City:"Havre de Grace",
Country:"US",
CountryCode:"",
CountrySubDivisionCode:"MD",
PostalCode:"21078",
PostalCodeSuffix:"",
Lat:"",
Long:"",
Tag:"",
Note:""
},
RemitToRef:"",
ClassRef:"",
SalesTermRef:"",
DueDate:"2014-08-28",
SalesRepRef:"",
PONumber:"",
FOB:"",
ShipMethodRef:"",
ShipDate:"",
TrackingNum:"",
GlobalTaxCalculation:"",
TotalAmt:"44.85",
HomeTotalAmt:"",
ApplyTaxAfterDiscount:"false",
TemplateRef:"",
PrintStatus:"NotSet",
EmailStatus:"EmailSent",
BillEmail:{
id:"",
address:"",
default:"",
tag:""
},
ARAccountRef:"",
Balance:"44.85",
FinanceCharge:"",
PaymentMethodRef:"",
PaymentRefNum:"",
PaymentType:"",
CheckPayment:"",
CreditCardPayment:"",
DepositToAccountRef:"",
DeliveryInfo:"",
DocNumber:"",
TxnDate:"2014-08-28",
DepartmentRef:"",
CurrencyRef:"USD",
ExchangeRate:"",
PrivateNote:"",
TxnStatus:"",
LinkedTxn:"",
Line:{
0:{
Id:"",
LineNum:"",
Description:"Hosting - Standard",
Amount:"14.95",
LinkedTxn:"",
DetailType:"SalesItemLineDetail",
PaymentLineDetail:"",
DiscountLineDetail:"",
TaxLineDetail:"",
SalesItemLineDetail:{
ServiceDate:"",
TaxInclusiveAmt:"",
SalesItemLineDetailEx:"",
ItemRef:"1",
ClassRef:"",
UnitPrice:"14.95",
RatePercent:"",
PriceLevelRef:"",
MarkupInfo:"",
Qty:"1",
UOMRef:"",
ItemAccountRef:"",
InventorySiteRef:"",
TaxCodeRef:""
},
DescriptionLineDetail:"",
ItemBasedExpenseLineDetail:"",
AccountBasedExpenseLineDetail:"",
DepositLineDetail:"",
PurchaseOrderItemLineDetail:"",
SalesOrderItemLineDetail:"",
ItemReceiptLineDetail:"",
JournalEntryLineDetail:"",
GroupLineDetail:"",
SubTotalLineDetail:"",
CustomField:"",
LineEx:""
},
1:{
Id:"",
LineNum:"",
Description:"Hosting - Standard",
Amount:"14.95",
LinkedTxn:"",
DetailType:"SalesItemLineDetail",
PaymentLineDetail:"",
DiscountLineDetail:"",
TaxLineDetail:"",
SalesItemLineDetail:{
ServiceDate:"",
TaxInclusiveAmt:"",
SalesItemLineDetailEx:"",
ItemRef:"1",
ClassRef:"",
UnitPrice:"14.95",
RatePercent:"",
PriceLevelRef:"",
MarkupInfo:"",
Qty:"1",
UOMRef:"",
ItemAccountRef:"",
InventorySiteRef:"",
TaxCodeRef:""
},
DescriptionLineDetail:"",
ItemBasedExpenseLineDetail:"",
AccountBasedExpenseLineDetail:"",
DepositLineDetail:"",
PurchaseOrderItemLineDetail:"",
SalesOrderItemLineDetail:"",
ItemReceiptLineDetail:"",
JournalEntryLineDetail:"",
GroupLineDetail:"",
SubTotalLineDetail:"",
CustomField:"",
LineEx:""
},
2:{
Id:"",
LineNum:"",
Description:"Hosting - Standard",
Amount:"14.95",
LinkedTxn:"",
DetailType:"SalesItemLineDetail",
PaymentLineDetail:"",
DiscountLineDetail:"",
TaxLineDetail:"",
SalesItemLineDetail:{
ServiceDate:"",
TaxInclusiveAmt:"",
SalesItemLineDetailEx:"",
ItemRef:"1",
ClassRef:"",
UnitPrice:"14.95",
RatePercent:"",
PriceLevelRef:"",
MarkupInfo:"",
Qty:"1",
UOMRef:"",
ItemAccountRef:"",
InventorySiteRef:"",
TaxCodeRef:""
},
DescriptionLineDetail:"",
ItemBasedExpenseLineDetail:"",
AccountBasedExpenseLineDetail:"",
DepositLineDetail:"",
PurchaseOrderItemLineDetail:"",
SalesOrderItemLineDetail:"",
ItemReceiptLineDetail:"",
JournalEntryLineDetail:"",
GroupLineDetail:"",
SubTotalLineDetail:"",
CustomField:"",
LineEx:""
},
3:{
Id:"",
LineNum:"",
Description:"",
Amount:"44.85",
LinkedTxn:"",
DetailType:"SubTotalLineDetail",
PaymentLineDetail:"",
DiscountLineDetail:"",
TaxLineDetail:"",
SalesItemLineDetail:"",
DescriptionLineDetail:"",
ItemBasedExpenseLineDetail:"",
AccountBasedExpenseLineDetail:"",
DepositLineDetail:"",
PurchaseOrderItemLineDetail:"",
SalesOrderItemLineDetail:"",
ItemReceiptLineDetail:"",
JournalEntryLineDetail:"",
GroupLineDetail:"",
SubTotalLineDetail:"",
CustomField:"",
LineEx:""
}
},
TxnTaxDetail:"",
TxnSource:"",
Id:"",
SyncToken:"",
MetaData:"",
CustomField:"",
AttachableRef:"",
domain:"",
status:"",
sparse:""
}
However, when my code runs, it fails on the $result = $this->_dataService->Add($invoice); line, and produces the following error.
Fatal error: Uncaught IdsException: [0]: 2014-08-28 13:24:33 - /var/www/emoxie.com/vendor/emoxie/quickbooks-sdk/src/DataService/DataService.php - 335 - CheckNullResponseAndThrowException - Response Null or Empty thrown in /var/www/emoxie.com/vendor/emoxie/quickbooks-sdk/src/Core/CoreHelper.php on line 95
DataService.php Line 335
CoreHelper::CheckNullResponseAndThrowException($responseBody);
Has anyone ever come across this and have a fix?
Here is an example of an Invoice for a US based company. Please match your invoice against this.-
<Invoice xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schema.intuit.com/finance/v3">
<DocNumber>96</DocNumber>
<TxnDate>2014-08-21</TxnDate>
<CurrencyRef name="Angolan Kwanza">AOA</CurrencyRef>
<ExchangeRate>1</ExchangeRate>
<Line>
<Id>1</Id>
<LineNum>1</LineNum>
<Amount>23.00</Amount>
<DetailType>SalesItemLineDetail</DetailType>
<SalesItemLineDetail>
<ItemRef name="bat">3</ItemRef>
<UnitPrice>23</UnitPrice>
<Qty>1</Qty>
<TaxCodeRef>10</TaxCodeRef>
</SalesItemLineDetail>
</Line>
<Line>
<Amount>23.00</Amount>
<DetailType>SubTotalLineDetail</DetailType>
<SubTotalLineDetail />
</Line>
<Line>
<Amount>0.46</Amount>
<DetailType>DiscountLineDetail</DetailType>
<DiscountLineDetail>
<PercentBased>true</PercentBased>
<DiscountPercent>2</DiscountPercent>
<DiscountAccountRef name="Discounts given">74</DiscountAccountRef>
</DiscountLineDetail>
</Line>
<TxnTaxDetail>
<TotalTax>2.25</TotalTax>
<TaxLine>
<Amount>2.25</Amount>
<DetailType>TaxLineDetail</DetailType>
<TaxLineDetail>
<TaxRateRef>20</TaxRateRef>
<PercentBased>true</PercentBased>
<TaxPercent>10</TaxPercent>
<NetAmountTaxable>22.54</NetAmountTaxable>
</TaxLineDetail>
</TaxLine>
</TxnTaxDetail>
<CustomerRef name="gg">3</CustomerRef>
<SalesTermRef>3</SalesTermRef>
<DueDate>2014-09-20</DueDate>
<GlobalTaxCalculation>TaxExcluded</GlobalTaxCalculation>
<TotalAmt>24.79</TotalAmt>
<HomeTotalAmt>24.79</HomeTotalAmt>
<PrintStatus>NotSet</PrintStatus>
<EmailStatus>NotSet</EmailStatus>
<BillEmail>
<Address>foop#gmail.com</Address>
</BillEmail>
<Balance>24.79</Balance>
<Deposit>0</Deposit>
<AllowIPNPayment>false</AllowIPNPayment>
<AllowOnlinePayment>false</AllowOnlinePayment>
<AllowOnlineCreditCardPayment>false</AllowOnlineCreditCardPayment>
<AllowOnlineACHPayment>false</AllowOnlineACHPayment>
</Invoice>
<IntuitResponse xmlns="http://schema.intuit.com/finance/v3" time="2014-08-20T23:23:07.692-07:00">
<Invoice domain="QBO" sparse="false">
<Id>14</Id>
<SyncToken>0</SyncToken>
<MetaData>
<CreateTime>2014-08-20T23:23:07-07:00</CreateTime>
<LastUpdatedTime>2014-08-20T23:23:07-07:00</LastUpdatedTime>
</MetaData>
<DocNumber>96</DocNumber>
<TxnDate>2014-08-21</TxnDate>
<CurrencyRef name="Angolan Kwanza">AOA</CurrencyRef>
<ExchangeRate>1</ExchangeRate>
<Line>
<Id>1</Id>
<LineNum>1</LineNum>
<Amount>23.00</Amount>
<DetailType>SalesItemLineDetail</DetailType>
<SalesItemLineDetail>
<ItemRef name="bat">3</ItemRef>
<UnitPrice>23</UnitPrice>
<Qty>1</Qty>
<TaxCodeRef>10</TaxCodeRef>
</SalesItemLineDetail>
</Line>
<Line>
<Amount>23.00</Amount>
<DetailType>SubTotalLineDetail</DetailType>
<SubTotalLineDetail />
</Line>
<Line>
<Amount>0.46</Amount>
<DetailType>DiscountLineDetail</DetailType>
<DiscountLineDetail>
<PercentBased>true</PercentBased>
<DiscountPercent>2</DiscountPercent>
<DiscountAccountRef name="Discounts given">74</DiscountAccountRef>
</DiscountLineDetail>
</Line>
<TxnTaxDetail>
<TotalTax>2.25</TotalTax>
<TaxLine>
<Amount>2.25</Amount>
<DetailType>TaxLineDetail</DetailType>
<TaxLineDetail>
<TaxRateRef>20</TaxRateRef>
<PercentBased>true</PercentBased>
<TaxPercent>10</TaxPercent>
<NetAmountTaxable>22.54</NetAmountTaxable>
</TaxLineDetail>
</TaxLine>
</TxnTaxDetail>
<CustomerRef name="gg">3</CustomerRef>
<SalesTermRef>3</SalesTermRef>
<DueDate>2014-09-20</DueDate>
<GlobalTaxCalculation>TaxExcluded</GlobalTaxCalculation>
<TotalAmt>24.79</TotalAmt>
<HomeTotalAmt>24.79</HomeTotalAmt>
<PrintStatus>NotSet</PrintStatus>
<EmailStatus>NotSet</EmailStatus>
<BillEmail>
<Address>foop#gmail.com</Address>
</BillEmail>
<Balance>24.79</Balance>
<Deposit>0</Deposit>
<AllowIPNPayment>false</AllowIPNPayment>
<AllowOnlinePayment>false</AllowOnlinePayment>
<AllowOnlineCreditCardPayment>false</AllowOnlineCreditCardPayment>
<AllowOnlineACHPayment>false</AllowOnlineACHPayment>
You need to send SalesItemLineDetail also in your line. Please refer these docs for sample invoice and basic tags that need to be sent.
https://developer.intuit.com/docs/0025_quickbooksapi/0050_data_services/030_entity_services_reference/invoice
Refer this for understanding all data types and key concepts-
https://developer.intuit.com/docs/0025_quickbooksapi/0050_data_services/020_key_concepts/0700_other_topics
https://developer.intuit.com/docs/0025_quickbooksapi/0050_data_services
Alternatively create an Invoice in UI and then read it using API explorer. Compare with your xml request. You will get an idea of how the correct payload should be.
Related
How to make ProgressBar progress in alertDialog?(Xamarin.Android)
I have the following code in alertDialog.SetPositiveButton alertConfirmTransfer.SetPositiveButton("ДА", delegate { ProgressBar progressBar = FindViewById<ProgressBar>(Resource.Id.progressBar); alertConfirmTransfer.Cancel(); MobileSellReference.Service1 service = new MobileSellReference.Service1(); progressBar.IncrementProgressBy(10); service.Url = settings.Synchronization.Msellurl; progressBar.IncrementProgressBy(10); byte[][] resultFromService = service.ToPPC(basedataZipName, objectId); progressBar.IncrementProgressBy(10); byte[] basedataZipFile = resultFromService[0]; byte[] dutybasedataZipFile = resultFromService[3]; byte[] tranbasedataZipFile = resultFromService[2]; byte[] vendbasedataZipFile = resultFromService[1]; progressBar.IncrementProgressBy(10); string basedataZipFullPath = GlobalVariables.fromserverFolderPath + "/" + basedataZipName; string dutybasedataZipFullPath = GlobalVariables.fromserverFolderPath + "/" + dutybasedataZipName; string tranbasedataZipFullPath = GlobalVariables.fromserverFolderPath + "/" + tranbasedataZipName; string vendbasedataZipFullPath = GlobalVariables.fromserverFolderPath + "/" + vendbasedataZipName; Helper.DeleteAllFromFolders(GlobalVariables.fromserverFolderPath, GlobalVariables.vendingFolderPath, GlobalVariables.tranFolderPath, GlobalVariables.debtFolderPath); progressBar.IncrementProgressBy(10); Helper.EmptyMobileSellDB(); progressBar.IncrementProgressBy(10); System.IO.File.WriteAllBytes(basedataZipFullPath, basedataZipFile); progressBar.IncrementProgressBy(10); System.IO.File.WriteAllBytes(dutybasedataZipFullPath, dutybasedataZipFile); progressBar.IncrementProgressBy(10); System.IO.File.WriteAllBytes(tranbasedataZipFullPath, tranbasedataZipFile); progressBar.IncrementProgressBy(10); System.IO.File.WriteAllBytes(vendbasedataZipFullPath, vendbasedataZipFile); progressBar.IncrementProgressBy(10); } I want the progress bar to increment in the places I specified . But when I run the app I have only progress bar circulating infinetly. I want to increase by and by. And After reaching 100 to dissapear. I've found too little information how to do that in Xamarin.Android and none of it helped me.
After the line where you findviewbyreference : ProgressBar progressBar = FindViewById<ProgressBar>(Resource.Id.progressBar); Set the max progress to 100 and then set the current progress to 0. progressBar.Max = 100; progressBar.Progress = 0; Then incrementing the progress should work. progressBar.IncrementProgressBy(10); // Try adding delay after/before you set the progress. // You can put the below code in a method and call that method after updating the progress bar. try { Thread.Sleep(2000); } catch (Exception exception) { Android.Util.Log.Error("Error",exception.Message); }
cs file MobileSellReference.Service1 service = new MobileSellReference.Service1(); service.Url = settings.Synchronization.Msellurl; progressBar.IncrementProgressBy(10); Thread.Sleep(2000); byte[][] resultFromService = service.ToPPC(basedataZipName, objectId); progressBar.IncrementProgressBy(50); Thread.Sleep(2000); byte[] basedataZipFile = resultFromService[0]; byte[] dutybasedataZipFile = resultFromService[3]; byte[] tranbasedataZipFile = resultFromService[2]; byte[] vendbasedataZipFile = resultFromService[1]; string basedataZipFullPath = GlobalVariables.fromserverFolderPath + "/" + basedataZipName; string dutybasedataZipFullPath = GlobalVariables.fromserverFolderPath + "/" + dutybasedataZipName; string tranbasedataZipFullPath = GlobalVariables.fromserverFolderPath + "/" + tranbasedataZipName; string vendbasedataZipFullPath = GlobalVariables.fromserverFolderPath + "/" + vendbasedataZipName; Helper.DeleteAllFromFolders(GlobalVariables.fromserverFolderPath, GlobalVariables.vendingFolderPath, GlobalVariables.tranFolderPath, GlobalVariables.debtFolderPath); progressBar.IncrementProgressBy(5); Thread.Sleep(2000); Helper.EmptyMobileSellDB(); progressBar.IncrementProgressBy(5); Thread.Sleep(2000); System.IO.File.WriteAllBytes(basedataZipFullPath, basedataZipFile); progressBar.IncrementProgressBy(10); Thread.Sleep(2000); System.IO.File.WriteAllBytes(dutybasedataZipFullPath, dutybasedataZipFile); progressBar.IncrementProgressBy(10); Thread.Sleep(2000); System.IO.File.WriteAllBytes(tranbasedataZipFullPath, tranbasedataZipFile); progressBar.IncrementProgressBy(10); Thread.Sleep(2000); System.IO.File.WriteAllBytes(vendbasedataZipFullPath, vendbasedataZipFile); The code above is in alert.SetPositiveButton("Yes" ....) method the axml file <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:gravity="center" android:layout_width="match_parent" android:layout_height="match_parent" android:minWidth="25px" android:minHeight="25px"> <LinearLayout android:orientation="vertical" android:gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="#+id/linearLayout1"> <ProgressBar style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="#+id/progressBar" /> <Button android:text="Взема данни" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="#+id/btnGetData" /> <Button android:text="Предава данни" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="#+id/btnTransferData" /> <Button android:text="Пълна синхронизация" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="#+id/btnFullSynchronization" /> </LinearLayout>
parse xml on blackberry 10
I need to parse file xml I have a link that display <labels> <label name="name1"> <description name="name1" url="http:............."/> <description name="name2" url="http:............."/> </label> <label name="name2"> <description name="name1" url="http:............."/> <description name="name2" url="http:............."/> </label> <labels> and then display it on List. I get the reply of url as xml contain using this method if (reply) { if (reply->error() == QNetworkReply::NoError) { int available = reply->bytesAvailable(); if (available > 0) { int bufSize = sizeof(char) * available + sizeof(char); QByteArray buffer(bufSize, 0); int read = reply->read(buffer.data(), available); response = QString(buffer); } } else { response = QString("Error: ") + reply->errorString() + QString(" status:") + reply->attribute( QNetworkRequest::HttpStatusCodeAttribute).toString(); } reply->deleteLater(); } How I can parse it and display item on list?
XMLDataModel directly parse the Xml into a QVariantMap: if (reply->error() == QNetworkReply::NoError) { XmlDataAccess xda; QVariantMap data = xda.load(reply).value<QVariantMap>(); if (xda.hasError()) { bb::data::DataAccessError error = xda.error(); qWarning() << " XML error: " << error.errorMessage(); } else{ emit requestSuccess(data); } } else { [...] Access it with data["label"].value().at(0), etc...
YouTube GData API for C# - Where do I find an example of a working batch query?
I have scoured the net for days, unsuccessfully just looking for a working example of a YouTube batch request using the C# API. So much of the documentation is out of date. These tell me what the XML should look like: https://developers.google.com/gdata/docs/batch https://developers.google.com/youtube/2.0/developers_guide_protocol_batch_processing and this is an example with Google Spreadsheets: https://developers.google.com/google-apps/spreadsheets/#updating_multiple_cells_with_a_batch_request Though I'm frightfully close time and again, I keep falling short of the mark with that gloriously descriptive GDataRequestException "Execution of request batch failed". Help!
Finally cracked it! Now, so you don't have to go through the same blind trial & terror experience I endured: using Google.GData.Client; using Google.GData.YouTube; using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Windows.Forms; using System.Xml; ... YouTubeService service = new YouTubeService("YouTube API Client", Properties.Settings.Default.YouTubeDeveloperKey); const String searchFields = "entry(id,title,link[#rel='alternate'],author(name,yt:userId),media:group(media:category(#label),media:credit,yt:videoid,yt:uploaderId),yt:statistics,yt:rating,gd:rating(#average),gd:comments/gd:feedLink(#countHint))"; String urlBase = String.Format("https://{0}gdata.youtube.com/feeds/api/videos", (testing ? "stage." : "")); String url = String.Format("{0}?v=2&fields={1}", urlBase, searchFields); Debug.Print(url); YouTubeQuery searchQuery = new YouTubeQuery(url); searchQuery.Query = keyword; searchQuery.NumberToRetrieve = Properties.Settings.Default.NumVideosToRetrieve; searchQuery.OrderBy = "relevance"; lblTagStats.Text = "Retrieving Top " + searchQuery.NumberToRetrieve + " Videos..."; YouTubeFeed searchResults = service.Query(searchQuery); DebugSaveToXml(searchResults, "searchResults"); foreach (YouTubeEntry entry in searchResults.Entries) { titles.Add(entry.Title.Text); if (entry.Statistics != null) viewCounts.Add(long.Parse(entry.Statistics.ViewCount)); if (entry.Comments != null) commentCounts.Add(long.Parse(entry.Comments.FeedLink.CountHint.ToString())); if (entry.Rating != null) ratings.Add(entry.Rating.Average); if (entry.YtRating != null) { likeCounts.Add(long.Parse(entry.YtRating.NumLikes)); dislikeCounts.Add(long.Parse(entry.YtRating.NumDislikes)); } videoURLs.Add(new Uri(entry.AlternateUri.ToString())); uploaders.Add(entry.Authors[0].Name); foreach (var category in entry.Media.Categories) if (category.Attributes["label"] != null) categories.Add(category.Attributes["label"].ToString()); } lblTagStats.Text = "Retrieving Subscriber Counts..."; AtomFeed batchRequest = new AtomFeed(searchResults); batchRequest.BatchData = new GDataBatchFeedData(); batchRequest.BatchData.Type = GDataBatchOperationType.query; foreach (YouTubeEntry entry in searchResults.Entries) { AtomEntry batchEntry = searchResults.CreateFeedEntry(); String uploaderId = entry.Media.ChildNodes.Find(x => x.Name == "yt:uploaderId").InnerText; batchEntry.Id = new AtomId("tag:youtube.com,2008:user:" + uploaderId); //not returned in search, so reconstruct it batchEntry.BatchData = new GDataBatchEntryData(); urlBase = String.Format("https://{0}gdata.youtube.com/feeds/api/users", (testing ? "stage." : "")); url = String.Format("{0}/{1}?v=2", urlBase, entry.Media.Credit.Value); Debug.Print(url); batchEntry.EditUri = url; batchRequest.Entries.Add(batchEntry); } DebugSaveToXml(batchRequest, "batchRequest_Profiles"); const String profileFields = ""; // "&fields=id,yt:username,yt:statistics(#subscriberCount)"; // YouTube API bug: cant handle colons in batch request fields urlBase = String.Format("https://{0}gdata.youtube.com/feeds/api/users/batch", (testing ? "stage." : "")); url = String.Format("{0}?v=2{1}", urlBase, profileFields); Debug.Print(url); YouTubeFeed profilesFeed = (YouTubeFeed)service.Batch(batchRequest, new Uri(url)); DebugSaveToXml(profilesFeed,"profilesFeed"); foreach (ProfileEntry entry in profilesFeed.Entries) if (entry.BatchData.Status.Code == 200) subscriberCounts.Add(long.Parse(entry.Statistics.SubscriberCount)); lblTagStats.Text = "Retrieving Full Descriptions..."; batchRequest = new AtomFeed(searchResults); batchRequest.BatchData = new GDataBatchFeedData(); batchRequest.BatchData.Type = GDataBatchOperationType.query; foreach (YouTubeEntry entry in searchResults.Entries) { AtomEntry batchEntry = searchResults.CreateFeedEntry(); batchEntry.Id = entry.Id; batchEntry.BatchData = new GDataBatchEntryData(); urlBase = String.Format("https://{0}gdata.youtube.com/feeds/api/videos", (testing ? "stage." : "")); url = String.Format("{0}/{1}?v=2", urlBase, entry.VideoId); Debug.Print(url); batchEntry.EditUri = url; batchRequest.Entries.Add(batchEntry); } DebugSaveToXml(batchRequest, "batchRequest_Descriptions"); const String descriptionFields = ""; // "&fields=media:group/media:description"; // YouTube API bug: cant handle colons in batch request fields urlBase = String.Format("https://{0}gdata.youtube.com/feeds/api/videos/batch", (testing ? "stage.":"")); url = String.Format("{0}?v=2{1}", urlBase, descriptionFields); Debug.Print(url); YouTubeFeed descriptionsFeed = (YouTubeFeed)service.Batch(batchRequest, new Uri(url)); DebugSaveToXml(descriptionsFeed,"descriptionsFeed"); foreach (YouTubeEntry entry in descriptionsFeed.Entries) if (entry.BatchData.Status.Code == 200) descriptions.Add(entry.Media.Description.Value); And FYI, this posts the following batch request XML for the profiles, which differs considerably from other samples, but still works like a charm: <feed xmlns="http://www.w3.org/2005/Atom" xmlns:batch="http://schemas.google.com/gdata/batch"> <link rel="http://schemas.google.com/g/2005#batch" type="application/atom+xml" /> <link rel="http://schemas.google.com/g/2005#post" type="application/atom+xml" /> <link rel="self" type="application/atom+xml" /> <link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" /> <batch:operation type="query" /> <entry xmlns:gd="http://schemas.google.com/g/2005"> <id>tag:youtube.com,2008:user:UC7UgD2HIyslGofSyX811fsQ</id> <link href="https://stage.gdata.youtube.com/feeds/api/users/inspiredtennis?v=2" rel="edit" type="application/atom+xml" /> </entry> <entry xmlns:gd="http://schemas.google.com/g/2005"> <id>tag:youtube.com,2008:user:UClXdxsQW4AqykIq2Fu4YXvQ</id> <link href="https://stage.gdata.youtube.com/feeds/api/users/azishertanto?v=2" rel="edit" type="application/atom+xml" /> </entry> <entry xmlns:gd="http://schemas.google.com/g/2005"> <id>tag:youtube.com,2008:user:UC1Diq4duvGuTUaGxk61-LOQ</id> <link href="https://stage.gdata.youtube.com/feeds/api/users/atamaiidotcom?v=2" rel="edit" type="application/atom+xml" /> </entry> ... </feed>
Flex 4.5 blendShader, Pixel Bender not refreshing
I'm following Joe Ward's Pixel Bender basics for Flex and AIR tutorial and updating it for Flex 4.5 Flashplayer 11. While working on the grainBlend section It Works great, if I have an "Alert" message pop up. Otherwise the shader does not refresh/update when the HSlider is changed. In other word: The script runs IF I have an active Alert message. If I remove the Alert Message, the blendShader only works once, and never updates afterwards. ScriptFlow: Init() OK create shader OK detect HSlider Change OK updateFilter() OK update Shader's turbulace value OK update image "noise" shader and redraw NOT WORKING I believe the following excerpt from the tutorial may be the issue. "...Because a shader object is cloned when you set the blendShader property of a display object, you cannot simply change the parameter of the original Shader object. You must also reassign the updated Shade object to the blendShader property...." shader.data.turbulence.value = [turbulence.value]; noise.blendMode = BlendMode.SHADER; noise.blendShader = shader; Flex code <?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" contentCreationComplete="init()" backgroundColor="0x666666"> <fx:Script> <![CDATA[ import mx.controls.Alert; import mx.events.SliderEvent; //Embed the Pixel Bender kernel in the output SWF [Embed(source="kernels/grainblend.pbj", mimeType="application/octet-stream")] private var GrainBlendKernel:Class; //Create the Shader object [Bindable] private var shader:Shader = new Shader( new GrainBlendKernel() ); private function init():void { //Set the slider values based on the parameter metadata turbulence.minimum = shader.data.turbulence.minValue; turbulence.maximum = shader.data.turbulence.maxValue; turbulence.value = shader.data.turbulence.defaultValue; turbulence.addEventListener( SliderEvent.CHANGE, updateFilter ); //Apply the blend noise.blendShader = shader; } private function updateFilter( event:Event ):void { trace(turbulence.value);//print slider //Alert.show("Hit"); shader.data.turbulence.value = [turbulence.value]; trace("shader's value: "+shader.data.turbulence.value); noise.blendMode = BlendMode.SHADER; noise.blendShader = shader; } ]]> </fx:Script> <s:VGroup width="100%"> <s:HGroup width="100%" height="100%" horizontalAlign="center" verticalAlign="top"> <s:VGroup> <mx:Canvas width="195" height="194" backgroundColor="#663300"/> <s:Label text="Background" textAlign="center" width="196"/> </s:VGroup> <s:VGroup> <s:Image source="img/noise.jpg" width="195" height="194"/> <s:Label text="Perlin noise" width="196" textAlign="center"/> </s:VGroup> <s:VGroup> <mx:Canvas width="195" height="194" backgroundColor="#663300"> <s:Image source="img/noise.jpg" id="noise" width="195" height="194"/> </mx:Canvas> <s:Label text="Grain blend" width="196" textAlign="center"/> </s:VGroup> </s:HGroup> <s:HGroup width="100%" horizontalAlign="center" verticalAlign="top"> <s:Label text="{turbulence.value}"/> <s:HSlider id="turbulence" width="200"/> </s:HGroup> </s:VGroup> Pixel Bender Kernel <languageVersion: 1.0;> kernel GrainBlend < namespace : "com.adobe.example"; vendor : "Adobe Systems Inc."; version : 1; description : "Creates a wood grain or marbleing effect"; > { input image4 background; input image4 noise; output pixel4 dst; parameter float turbulence < maxValue : 500.0; minValue : 0.0; defaultValue : 150.0; >; void evaluatePixel() { pixel4 a = sampleNearest(background, outCoord()); pixel4 b = sampleNearest(noise, outCoord()); float alpha = a.a; //save the original alpha if( (b.a > 0.0) && (a.a > 0.0)){ float seed = outCoord().x + (((b.r + b.g + b.b)/3.0) * turbulence); float grain = (0.7 * sin(seed) + 0.3 * sin(2.0 * seed + 0.3) + 0.2 * sin(3.0 * seed + 0.2)); dst = sampleNearest(background, outCoord()) * (grain + 0.5); dst.a = alpha; //restore the original alpha } else { //Just copy the background pixel outside the area of the noise image dst = sampleNearest(background, outCoord()); } } }
I gave up on blendShader. I recreated it using filters. It works now. Also, I dynamically created the brown background and the perlin noise. See below! <?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" creationComplete="init()" backgroundColor="0x666666"> <fx:Script> <![CDATA[ import mx.events.SliderEvent; import spark.filters.ShaderFilter; //Embed the Pixel Bender kernel in the output SWF [Embed(source="kernels/grainblend.pbj", mimeType="application/octet-stream")] private var GrainBlendKernel:Class; //Create the Shader object private var shader:Shader = new Shader( new GrainBlendKernel() ); private var shaderFilter:ShaderFilter = new ShaderFilter(shader); private var myBrown:BitmapData; private var myPerlin:BitmapData; private function init():void { myPerlin = new BitmapData(200, 200, false, 0x00CCCCCC); myBrown = new BitmapData(200, 200, false, 0x00663300); //Set the slider values based on the parameter metadata turbulence.minimum = shader.data.turbulence.minValue; turbulence.maximum = shader.data.turbulence.maxValue; turbulence.value = shader.data.turbulence.defaultValue; turbulence.addEventListener( SliderEvent.CHANGE, updateFilter ); myPerlin.perlinNoise(100, 80, 6, Math.floor(Math.random() * 10), false, true, 7, true, null); //Set the displayed images to the perlinNoise perlinNoise.source = myGrain.source =myPerlin; //Set the background image to Brown backGround.source = myBrown; shader.data.background.input = myBrown; myGrain.filters = [shaderFilter]; } private function updateFilter( event:Event ):void { shader.data.turbulence.value = [turbulence.value]; myGrain.filters = [shaderFilter]; } ]]> </fx:Script> <s:VGroup width="100%"> <s:HGroup width="100%" height="100%" horizontalAlign="center" verticalAlign="top"> <s:VGroup> <s:BitmapImage id="backGround" width="200" height="200"/> <s:Label text="Background" textAlign="center" width="200"/> </s:VGroup> <s:VGroup> <s:BitmapImage id="perlinNoise" width="200" height="200"/> <s:Label text="Perlin noise" width="200" textAlign="center"/> </s:VGroup> <s:VGroup> <s:BitmapImage id="myGrain" width="200" height="200" /> <s:Label text="Grain blend" width="200" textAlign="center"/> </s:VGroup> </s:HGroup> <s:HGroup width="100%" horizontalAlign="center" verticalAlign="top"> <s:Label text="{turbulence.value}"/> <s:HSlider id="turbulence" width="200"/> </s:HGroup> </s:VGroup> </s:Application>`
How to add a copy/paste context menu to browser element in XULRunner?
I would like to allow the user of my XULRunner based app to be able to do copy/paste via a context menu. Keyboard shortcuts Ctrl-C and Ctrl-V are already working fine
Here it is without flash. The getInputSelection function is from here: Is there an Internet Explorer approved substitute for selectionStart and selectionEnd?. <?xml version="1.0"?> <?xml-stylesheet href="chrome://global/skin/" type="text/css"?> <window id="mywin" title="my app" width="800" height="600" persist="screenX screenY width height sizemode" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <popupset> <menupopup id="clipmenu"> <menuitem label="Copy" oncommand="copy()"/> <menuitem label="Paste" oncommand="paste();"/> </menupopup> </popupset> <browser type="content-primary" src="http://127.0.0.1/" flex="1" disablehistory="true" id="browserId" context="clipmenu" /> <script> <![CDATA[ function copy() { var tabBrowser = document.getElementById("browserId"); var selectedTagName = tabBrowser.contentWindow.document.activeElement.tagName; var windowObj; if(selectedTagName == "IFRAME") { windowObj = tabBrowser.contentWindow.frames[tabBrowser.contentWindow.document.activeElement.name]; } else { windowObj = document.getElementById("browserId").contentWindow; } var selectedText = windowObj.getSelection(); if(!selectedText || selectedText == "") { var focused = windowObj.document.activeElement; if(focused && focused.value) { selectedText = getSelectionFromInput(focused); } } //alert(selectedText + "---"); const clipHelper = Components.classes["#mozilla.org/widget/clipboardhelper;1"].getService(Components.interfaces.nsIClipboardHelper); clipHelper.copyString(selectedText); } function getSelectionFromInput(focused) { var focusedValue = focused.value; var sel = getInputSelection(focused); var selectedText = ""; if(focusedValue.length == (sel.end)) { selectedText = focusedValue.substring(sel.start); } else { selectedText = focusedValue.substring(sel.start, (sel.end)); } return selectedText; } function paste() { var clip = Components.classes["#mozilla.org/widget/clipboard;1"].getService(Components.interfaces.nsIClipboard); var trans = Components.classes["#mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable); trans.addDataFlavor("text/unicode"); clip.getData(trans, clip.kGlobalClipboard); var str = new Object(); var len = new Object(); trans.getTransferData("text/unicode",str,len); str = str.value.QueryInterface(Components.interfaces.nsISupportsString); str = str.data.substring(0, len.value / 2); var focused = document.commandDispatcher.focusedElement; var focusedValue = focused.value; var sel = getInputSelection(focused); focused.value = focusedValue.substring(0,sel.start) + str + focusedValue.substring(sel.end); } function getInputSelection(el) { var start = 0, end = 0, normalizedValue, range, textInputRange, len, endRange; if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") { start = el.selectionStart; end = el.selectionEnd; } else { range = document.selection.createRange(); if (range && range.parentElement() == el) { len = el.value.length; normalizedValue = el.value.replace(/\r\n/g, "\n"); // Create a working TextRange that lives only in the input textInputRange = el.createTextRange(); textInputRange.moveToBookmark(range.getBookmark()); // Check if the start and end of the selection are at the very end // of the input, since moveStart/moveEnd doesn't return what we want // in those cases endRange = el.createTextRange(); endRange.collapse(false); if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) { start = end = len; } else { start = -textInputRange.moveStart("character", -len); start += normalizedValue.slice(0, start).split("\n").length - 1; if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) { end = len; } else { end = -textInputRange.moveEnd("character", -len); end += normalizedValue.slice(0, end).split("\n").length - 1; } } } } return { start: start, end: end }; } ]]> </script> </window> Updated to support iframes.
Create menu using the code below. <popupset> <menupopup id="clipmenu"> <menuitem label="Copy" oncommand="copy();"/> <menuseparator/> <menuitem label="paste" oncommand="paste();"/> </menupopup> </popupset> <browser type="content" src="chrome://myapp/content/theme1/index.html" flex="1" context="clipmenu"/> Connect it to whatever you want, here am making it the context menu of the browser element by giving the id of menu in the context attribute of the browser element. Then for each menu you can give the command to execute in oncommand event. We have given the function copy and paste. Then write the code to copy text from whatever element you want or if you want to copy the selected data only. See the below link for copying command. http://www.deluxeblogtips.com/2010/06/javascript-copy-to-clipboard.html
One more example for "Copy" context menu in XULRunner under SWT Browser (code was implemented for Eclipse v3.5.1, XULRunner v1.8.1.3): Browser browser = new Browser(parent, style | SWT.MOZILLA); Menu menu = new Menu(browser); MenuItem item = new MenuItem(menu, SWT.NONE); item.setText("Copy"); item.addSelectionListener(new SelectionListener() { #Override public void widgetSelected(SelectionEvent e) { nsIWebBrowser webBrowser = (nsIWebBrowser) browser.getWebBrowser(); nsIInterfaceRequestor req = (nsIInterfaceRequestor) webBrowser.queryInterface(nsIInterfaceRequestor.NS_IINTERFACEREQUESTOR_IID); nsIDocShell docShell = (nsIDocShell) req.getInterface(nsIDocShell.NS_IDOCSHELL_IID); nsIClipboardCommands cmds = (nsIClipboardCommands) docShell.queryInterface(nsIClipboardCommands.NS_ICLIPBOARDCOMMANDS_IID); cmds.copySelection(); } #Override public void widgetDefaultSelected(SelectionEvent e) { } }); browser.setMenu(menu);