How can I get name from link? - html

I'm writing on objective-C.
I have WebView and local file index.html has
<a href='http://www.google.com' name="666">
How can I get the name attribute?
Thanks!

It depends on when/by what you need to get the name. If you need the name when someone clicks on the link, you can set up some JavaScript that runs when the link is clicked (onclick handler). If you just have the html string, you can use regular expressions to parse the document and pull out all of the name attributes. A good regular expression library for Objective-C is RegexKit (or RegexKitLite on the same page).
The regular expression for parsing the name attribute out of a link would look something like this:
/<a[^>]+?name="?([^" >]*)"?>/i
EDIT: The javascript for getting a name out of a link when someone clicked it would look something like this:
function getNameAttribute(element) {
alert(element.name); //Or do something else with the name, `element.name` contains the value of the name attribute.
}
This would be called from the onclick handler, something like:
My Link
If you need to get the name back to your Objective-C code, you could write your onclick function to append the name attribute to the url in the form of a hashtag, and then trap the request and parse it in your UIWebView delegate's -webView:shouldStartLoadWithRequest:navigationType: method. That would go something like this:
function getNameAttribute(element) {
element.href += '#'+element.name;
}
//Then in your delegate's .m file
- (BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType {
NSArray *urlParts = [[request URL] componentsSeparatedByString:#"#"];
NSString *url = [urlParts objectAtIndex:0];
NSString *name = [urlParts lastObject];
if([url isEqualToString:#"http://www.google.com/"]){
//Do something with `name`
}
return FALSE; //Or TRUE if you want to follow the link
}

Related

Dynamic XML Template in TVML/TVJS

Does anyone know how to Dynamically generate a template in an apple tv app using TVJS/TVML? Basically I want to hit my API, get back an array of objects and then insert that data into my XML template.
I've been searching for info on how to accomplish it but have come up short. I've found many tutorials that use hard coded images, videos, etc but nothing dynamically generated.
Any help would be appreciated.
Finally, I've figured this out. It wouldn't be difficult to generate a template on-the-fly, but instead I wanted to reuse the Presenter and the ResourceLoader, and to have the template as a *.xml.js file. Here is the solution I managed to arrive at.
For the initial view, I used a catalogTemplate, as demonstrated in Ray Wenderlich's tutorial. Instead of conference talks, however, I was displaying categories of men's and women's merchandise. Once a category was selected, I wanted to display a stackTemplate with a number of options for that category. The problem was how to pass any information, the title of the category in the simplest case, to the second template.
In the first template, I had the lockups configured like so:
<lockup categoryTitle="Women: Dresses" categoryDir="w-dresses">
<img src="${this.BASEURL}images/dresses.jpg" width="230" height="288" />
<title>Dresses</title>
</lockup>
In application.js, I had a listener attached, in the same way how tutorials show:
doc.addEventListener("select", Presenter.load.bind(Presenter));
Here is the second template (Category.xml.js):
var Template = function(categoryTitle) {
return `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<stackTemplate>
<banner>
<title>${categoryTitle}</title>
</banner>
</stackTemplate>
</document>`
}
This is a JavaScript, so in your case you can pass into the function, say, an array of values and then construct the template accordingly. The tricky part was to pass a value.
First, I made a couple of changes to the ResourceLoader (this can be done better, of course, it's just a proof of concept). I simply added categoryTitle as an additional parameter to the top-level function and when calling the Template:
ResourceLoader.prototype.loadResource = function(resource, callback, categoryTitle) {
var self = this;
evaluateScripts([resource], function(success) {
if(success) {
var resource = Template.call(self, categoryTitle);
callback.call(self, resource);
} else {
var title = "Resource Loader Error",
description = `Error loading resource '${resource}'. \n\n Try again later.`,
alert = createAlert(title, description);
navigationDocument.presentModal(alert);
}
});
}
Finally, in the Presenter, in the load, I am passing categoryTitle to the resourceLoader:
load: function(event) {
var self = this,
ele = event.target,
categoryTitle = ele.getAttribute("categoryTitle");
if (categoryTitle) {
resourceLoader.loadResource(`${baseURL}templates/Category.xml.js`, function(resource) {
var doc = self.makeDocument(resource);
self.pushDocument(doc);
}, categoryTitle);
}
},
This works for me.
One final note: for some categories, I had titles with an ampersand, like 'Tops & T-shirts'. Naturally, I replaced the ampersand with an XML entity: 'Tops & T-shirts'. This, however, didn't work, probably because this string was decoded twice: the first time the entity was turned into an ampersand, and on the second pass the single ampersand was flagged as an error. What worked for me was this: 'Tops &amp; T-shirts'!
It is simple if you are using atvjs.
// create your dynamic page
ATV.Page.create({
name: 'homepage',
url: 'path/to/your/json/data',
template: function(data) {
// your dynamic template
return `<document>
<alertTemplate>
<title>${data.title}</title>
<description>${data.description}</description>
</alertTemplate>
</document>`;
}
});
// later in your app you can navigate to your page by calling
ATV.Navigation.navigate('homepage');
Disclaimer: I am the creator and maintainer of atvjs and as of writing this answer, it is the only JavaScript framework available for Apple TV development using TVML and TVJS. Hence I could provide references only from this framework. The answer should not be mistaken as a biased opinion.
I'm using PHP to generate the TVML files dynamically, configuring the output as text/javascript format:
<?php
header("Content-type: application/x-javascript");
[run your PHP API calls here]
$template = '<?xml version="1.0" encoding="UTF-8" ?>
<document>
... [use PHP variables here] ...
</document>';
echo "var Template = function() { return `". $template . "`}";
?>
You can dynamically generate a template by creating a dynamic string that represents the xml in a TVML template.
Review the code in here: https://developer.apple.com/library/prerelease/tvos/samplecode/TVMLCatalog/Listings/client_js_Presenter_js.html#//apple_ref/doc/uid/TP40016505-client_js_Presenter_js-DontLinkElementID_6
This file has functions that can be used to create an XML document that can represent a view.
You can make an XMLHttpRequest (ex: consuming API JSon calls through TVJS-tvOS) bring back some JSON data and then dynamically generate an XML document that conforms to one of the TVML templates. Parse it into an XML document and then navigate to the document.

UIWebView ignores inline anchor/link

I'm using a UIWebViewthat loads HTML from a database string using webView.loadHTMLString(self.htmlContent, baseURL: nil)
The htmlContent contains the following:
<ul class="anchorNavigation">
<li>
1. Inline Test Link
</li>
<li>
2. Inline Test Link
</li>
...
</ul>
... and later in the HTML:
...
...
However, whenever I click the inline link in the webView nothing happens.
What I've tried so far:
Changing the anchor tag to 'real' valid W3C HTML. E.g. <a id='parsys_47728'>Test</a>
Saving the HTML to a file in the temp directory and loading it using loadRequest(). E.g. let path = tempDirectory.URLByAppendingPathComponent("content.html") and webView.loadRequest(NSURLRequest(URL: path))
Intercepting the loadRequest method by implementing the func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool delegate. The request.URL says something strange like: "applewebdata://1D9D74C2-BBB4-422F-97A7-554BCCD0055A#parsys_47728"
I don't have any idea anymore how to achieve this. I know from previous projects that local HTML files in the bundle work with inline links. I just cannot figure out why this doesn't work.
Help much appreciated! Thank you!
If there's a fragment (e.g., #anchorName), then use JavaScript to scroll. Otherwise, assume it's a link without a fragment and use openURL.
// UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if (navigationType == UIWebViewNavigationTypeLinkClicked ) {
// NSLog(#"request.URL %#",request.URL); // e.g., file:///.../myApp.app/#anchorName
NSString *anchorName = request.URL.fragment; // e,g, "anchorName"
if ( anchorName ) {
[webView stringByEvaluatingJavaScriptFromString:[NSString swf:#"window.location.hash='%#';",anchorName]];
return NO;
} else { // assume http://
[[UIApplication sharedApplication] openURL:[request URL]];
return NO;
}
}
return YES;
}
I'm still looking for a way to have the scroll position change smoothly (animated) rather than jumping.

Populate a hyperlink attributed string for UIActivityViewController activities

There are lots of answers on SO that show devs how to make a string from HTML content or place a URL in a string, but my question is how to make an HTML string.
I'm trying to create a string that will return in HTML format or at least not show the URL.
So for example web devs would do this to hide the URL:
Visit Us at Google.com!
I can easily translate that to a string by doing so:
NSString *urlLink = #"www.google.com";
NSString *string = [NSString stringWithFormat:#"Visit Us at %#", urlLink];
But that doesn't replace the link with a hyperlink word of my choosing.
I'm aware that the device dictates if its a hyperlink depending on how you display it. i.e., text fields, text views, or you can force open it etc.
What i'm trying to do is:
#define APPSTORELINK #"www.appstorelink.com"
#implementation Config
+(NSString *)appstorelink {
return APPSTORELINK;
}
+(NSString *)mmsmetadata {
NSString *string = [NSString stringWithFormat:#"I shared this publication with the [Name of my iPhone App] iPhone App", APPSTORELINK];
return string;
}
So I can easily call it here or app wide:
NSArray *shareItems;
UIImage *snapshot = [self imageFromView:self.view];
shareItems = #[[Config mmsmetadata], snapshot];
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:shareItems applicationActivities:nil];
activityController.excludedActivityTypes = #[UIActivityTypePostToFlickr, UIActivityTypeAssignToContact, UIActivityTypeMail, UIActivityTypePostToVimeo];
[activityController setCompletionWithItemsHandler:(UIActivityViewControllerCompletionWithItemsHandler)^(NSString *string, BOOL completed) {
So in short, how can I make the string HTML format out of the box? My main concern is I want to hide the URL and replace it with an HTML tag, or otherwise if you have a better solution. Can't find anything on SO.
Any thoughts? I'm probably overthinking this. I'm sure theres an easier way
EDIT
Before even posting I have been aware of NSAttributedString and that was the first thing I attempted. However, the issue isn't setting an attribute, thats the easy part, the part that is defining my question is how to set it so it will DISPLAY as attributed when using activities in the UIActivityViewController
Here is how I set it, but the outcome was the same as above so I figured it would be easier to use an HTML tag:
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:#"I shared this publication with the Army Pubs iPhone App!"];
NSRange selectedRange = NSMakeRange(0, [string length]);
NSURL *linkURL = [NSURL URLWithString:APPSTORELINK];
[string beginEditing];
[string addAttribute:NSLinkAttributeName
value:linkURL
range:selectedRange];
[string addAttribute:NSForegroundColorAttributeName
value:[UIColor blueColor]
range:selectedRange];
[string addAttribute:NSUnderlineStyleAttributeName
value:[NSNumber numberWithInt:NSUnderlineStyleSingle]
range:selectedRange];
[string endEditing];
return string;
However, it still displays as plain text in the Message or Mail composers. So think MFMailComposeViewControllerDelegate how there is a setting for isHTML. If it's set to yes it strips all the HTML tags and displays the text as a hyperlink. For example:
MFMailComposeViewController *mc = [[MFMailComposeViewController alloc] init];
mc.mailComposeDelegate = self;
[mc setSubject:emailTitle];
[mc setMessageBody:messageBody isHTML:YES];
I want to emulate that when the activities are called from within a UIActivityViewController
This is the output currently even if I do it with the attributed string I tried first it just displays as plain text by stripping the HTML tag but doesn't make it a link
See link option in attributed strings.
The link attribute specifies an arbitrary object that is passed to the NSTextView method
clickedOnLink:atIndex: when the user clicks in the text range
associated with the NSLinkAttributeName attribute. The text view’s
delegate object can implement textView:clickedOnLink:atIndex: or
textView:clickedOnLink: to process the link object. Otherwise, the
default implementation checks whether the link object is an NSURL
object and, if so, opens it in the URL’s default application.

How to get object referece of UIPickerView when it create through html select tag

I have a UIWebview contains a html "select" tag, which is shown as a on the screen.
When I click the dropdown, the UIWebview brings up a UIWebSelectSinglePicker View automatically, which is shown as .
I want to change the picker view background color and text color. How can I achieve this goal?
I tried to listen on UIKeyboardWillShowNotification event, but at that moment, this view has not been created.
Thanks in advance for any helps.
I managed to resolve the issue myself.
If someone also want to change the UIPickView on the fly, please take a look:
First, add a listener on UIKeyboardWillShowNotification event.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(_pickerViewWillBeShown:) name:UIKeyboardWillShowNotification object:nil];
Second, when notification fired, call change background color method after delay. <-- This is very important, if call method without delay, the pickview does not exist at that moment.
- (void)_pickerViewWillBeShown:(NSNotification*)aNotification {
[self performSelector:#selector(_resetPickerViewBackgroundAfterDelay) withObject:nil afterDelay:0];
}
Third, go through the UIApplication windows and find out pickerView. And you can change what ever you want for pickerView.
-(void)_resetPickerViewBackgroundAfterDelay
{
UIPickerView *pickerView = nil;
for (UIWindow *uiWindow in [[UIApplication sharedApplication] windows]) {
for (UIView *uiView in [uiWindow subviews]) {
pickerView = [self _findPickerView:uiView];
}
}
if (pickerView){
[pickerView setBackgroundColor:UIColorFromRGB(0x00FF00)];
}
}
(UIPickerView *) _findPickerView:(UIView *)uiView {
if ([uiView isKindOfClass:[UIPickerView class]] ){
return (UIPickerView*) uiView;
}
if ([uiView subviews].count > 0) {
for (UIView *subview in [uiView subviews]){
UIPickerView* view = [self _findPickerView:subview];
if (view)
return view;
}
}
return nil;
}
Hope it will help.
I believe I've come up with an alternate solution to this problem. There are certain circumstances with the other solution proposed where the label colours appear incorrect (using the system default instead of the overridden colour). This happens while scrolling the list of items.
In order to prevent this from happening, we can make use of method swizzling to fix the label colours at their source (rather than patching them after they're already created).
The UIWebSelectSinglePicker is shown (as you've stated) which implements the UIPickerViewDelegate protocol. This protocol takes care of providing the NSAttributedString instances which are shown in the picker view via the - (NSAttributedString *)pickerView:(UIPickerView *)pickerView attributedTitleForRow:(NSInteger)row forComponent:(NSInteger)component method. By swizzling the implementation with our own, we can override what the labels look like.
To do this, I defined a category on UIPickerView:
#implementation UIPickerView (LabelColourOverride)
- (NSAttributedString *)overridePickerView:(UIPickerView *)pickerView
attributedTitleForRow:(NSInteger)row
forComponent:(NSInteger)component
{
// Get the original title
NSMutableAttributedString* title =
(NSMutableAttributedString*)[self overridePickerView:pickerView
attributedTitleForRow:row
forComponent:component];
// Modify any attributes you like. The following changes the text colour.
[title setAttributes:#{NSForegroundColorAttributeName : [UIColor redColor]}
range:NSMakeRange(0, title.length)];
// You can also conveniently change the background of the picker as well.
// Multiple calls to set backgroundColor doesn't seem to slow the use of
// the picker, but you could just as easily do a check before setting the
// colour to see if it's needed.
pickerView.backgroundColor = [UIColor yellowColor];
return title;
}
#end
Then using method swizzling (see this answer for reference) we swap the implementations:
[Swizzle swizzleClass:NSClassFromString(#"UIWebSelectSinglePicker")
method:#selector(pickerView:attributedTitleForRow:forComponent:)
forClass:[UIPickerView class]
method:#selector(overridePickerView:attributedTitleForRow:forComponent:)];
This is the Swizzle implementation I developed based off the link above.
#implementation Swizzle
+ (void)swizzleClass:(Class)originalClass
method:(SEL)originalSelector
forClass:(Class)overrideClass
method:(SEL)overrideSelector
{
Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
Method overrideMethod = class_getInstanceMethod(overrideClass, overrideSelector);
if (class_addMethod(originalClass,
originalSelector,
method_getImplementation(overrideMethod),
method_getTypeEncoding(overrideMethod))) {
class_replaceMethod(originalClass,
overrideSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
}
else {
method_exchangeImplementations(originalMethod, overrideMethod);
}
}
#end
The result of this is that when a label is requested, our override function is called, which calls the original function, which conveniently happens to return us a mutable NSAttributedString that we can modify in anyway we want. We could completely replace the return value if we wanted to and just keep the text. Find the list of attributes you can change here.
This solution allows you to globally change all the Picker views in the app with a single call removing the need to register notifications for every view controller where this code is needed (or defining a base class to do the same).

Get full href list from a QWebPage

I am trying to use a QWebPage (from QWebKit) to list all the href attributes from A tags with the full URL. At the moment, I do this:
QWebElementCollection collection = webPage->mainFrame()->findAllElements("a");
foreach (QWebElement element, collection)
{
QString href = element.attribute("href");
if (!href.isEmpty())
{
// Process
}
}
But the problem is that href could be a full URL, just a page, a URL with / at the front, or a URL with ../ at the front. Is there a way to parse all these different URLs to produce the full URL in a QString or a QUrl?
QWebFrame has a function named baseUrl which will provide a QUrl object for helping you to resolve the urls in the page.
With it you can call the resolved function with a separate QUrl (built from the href) to resolve the url. If the url is relative, it converts it to the resolved absolute url. If it isn't relative, it returns it with no modifications instead.
Here's an (untested) example based on the code you provided:
QUrl baseUrl = webPage->mainFrame()->baseUrl();
QWebElementCollection collection = webPage->mainFrame()->findAllElements("a");
foreach (QWebElement element, collection)
{
QString href = element.attribute("href");
if (!href.isEmpty())
{
QUrl relativeUrl(href);
QUrl absoluteUrl = baseUrl.resolved(relativeUrl);
// Process
}
}