Showing posts with label JavaScript. Show all posts
Showing posts with label JavaScript. Show all posts

Friday, August 12, 2022

Building a Bridge between Swift and JS

This was a challenge for me to make Swift talk to JS or JS to Swift. I have been working on an iOS app for years. With so many years of practice, I think I am very good at a deep technical level. I have also done a lot of html and JS programming. What I know about JS is mainly based on what I need to do on my blog.

In my app updates, what I need to do is to provide some information to users about how to use the app. An HTML view is a good choice for this purpose. However, I encountered a challenge that some of the information HTML needs is actually available easily on the Swift side. How can I let JS make a request to Swift, and then Swift pass the requested information back to JS?

This is what I mean by a bridge between Swift and JS.

Working Environment

In my project, I use a simple ViewController with a WKWebView UI component. The class is a very simple one. In the viewDidLoad event, an html file will be loaded into WKWebView. The content defined in the html file will be displayed on the view screen.

  1. class MyWebViewController: UIViewController
  2. {
  3.   @IBOutlet weak var webView: WKWebView!
  4.   {
  5.     didSet {
  6.       setup() // Place 1: magic will be injected from here.
  7.     }
  8.   }
  9.   var htmlUrl: URL? = nil
  10.   override func viewDidLoad()
  11.   {
  12.      super.viewDidLoad()
  13.      guard let url = htmlUrl else { return }
  14.      webView.loadFileURL(
  15.            url
  16.            allowingReadAccessTo: url)
  17.      let request = URLRequest(url: url)
  18.      webView.load(request)
  19.   }
  20.    override func viewWillDisappear(_ animated: Bool)
  21.    {
  22.        super.viewWillDisappear(animated)
  23.        // Place 2: some codes to be added here.
  24.    }
  25. }

My html file contains information about how JS is loaded. Normally, a JS script file is defined in the head section. For example,

  1. <head>
  2.   <meta charset='UTF-8'/>
  3.   <!-- JS functions used in html  -->
  4.   <script type="text/javascript" src="../myScriptFile.js"></script>
  5.   ...
  6. </head>

As you can see from the above structure, I have a view controller with a web viewer, in Swift code to present html content. The JS files which will be referenced from html are in my project bundle. This is my working environment.

A Case Study

Here is my case. In my html content, there is one element for displaying my app version.

<h2 class="center">Version <span id="appVersion">to be filed</span></h2>

This could be easily done by using a JS function like this:

  1. function swift2JSCallFor(appVersion) {
  2.    let e = document.getElementById("appVersion");
  3.    e.innerHTML = appVersion;
  4. }

The version string can be easily obtained in Swift code. How can I make a call from Swift to this JS function with version information?

WKScriptMessageHandler as a Gateway

Fortunately, Apple provides an API for the case of my view controller with WKWebView. WKScriptMessageHandler is the name of this API. This handler protocol can be implemented as a message channel gateway between Swift and JS.

This gateway could be implemented in my view controller class. There are some codes required to set it up. The disadvantage of doing this in my view controller is that I would have to repeat a block of similar code again and again in other view controller classes if I needed to do a similar thing. Another point is that adding some codes just for this kind of gateway would pollute my view controller class and make it harder to read and maintain.

Build My Gateway Class

After my thorough study and tests of this framework and a good understanding of what is required, I decided to create a new class to encapsulate the business logic or structure outline inside my class and expose the necessary settings for customizing.

  1. import WebKit
  2. typealias ScriptMessageHandlerBlock =
  3.    ((WKUserContentController,
  4.      WKScriptMessage) -> ())
  5. class MyWKScriptBridgeHelper: NSObject
  6. {
  7.    init(
  8.        observer: String,
  9.        scriptMessageHandlerBlock: ScriptMessageHandlerBlock?
  10.    )
  11.    {
  12.        webObserver = observer
  13.        wKScriptMessageHandlerBlock = scriptMessageHandlerBlock
  14.    }
  15.    private var webObserver: String = ""
  16.    private var wKScriptMessageHandlerBlock:
  17.    ScriptMessageHandlerBlock? = nil
  18.    var webObserverName: String {
  19.        return webObserver
  20.    }
  21. }
  22. extension WKScriptMessageHandler: WKScriptMessageHandler
  23. {
  24.    func userContentController(
  25.        _ userContentController: WKUserContentController,
  26.        didReceive message: WKScriptMessage)
  27.    {
  28.        guard let block = wKScriptMessageHandlerBlock else
  29.        {
  30.            return
  31.        }
  32.        block(userContentController, message)
  33.    }
  34. }

The class logic is simple. Only two parameters are required to create an instance of this class. One is a webObserver name, which will be called from JS to Swift to the gateway implemented in this class. Another one is a block of code where Swift information will be gethered or prepared and JS functions will be called.

Set it up on the Swift Side

OK, so far so good. It is time to put it all together in practice!

In my view controller class, there are two places where I will put some code. The first place is to set up my gateway instance of the MyWKScriptBridgeHelper class.

  1. class MyViewController
  2. {
  3.  private var scriptHelper: MyWKScriptBridgeHelper!
  4.    ...
  5.  private func setup()
  6.  {
  7.    let webObserver = "appVersion"
  8.    let block: ScriptMessageHandlerBlock
  9.        = {
  10.            [weak self] (userController, message) in
  11.            guard let this = self else {
  12.                return
  13.            }
  14.            // Make sure that it is html js function callback
  15.            guard message.name ==
  16.                    webObserver else {
  17.                return
  18.            }
  19.            // get app version string
  20.            let v = ... // get app version
  21.            let jsFunc = String(
  22.                format: "swift2JSCallFor('%@')",
  23.                v)
  24.            // Sends back app version to hml js function
  25.            this.webView.evaluateJavaScript(
  26.                jsFunc,
  27.                completionHandler: nil)
  28.        }
  29.        scriptHelper = MyWKScriptBridgeHelper(
  30.            observer: webObserver,
  31.            scriptMessageHandlerBlock: block)
  32.  }
  33.  ...
  34. }

In this setup(), two parameters are prepared for creating the instance of MyWKScriptBridgeHelper. One is the name of "webObserver" as the message name of the gateway. Another one is the block to verify the right message is received, to prepare the app version, and finally call a JS function "swift2JSCallFor()" to send the app version to the JS side.

The second location is in the view controller disappear event, where you should perform some cleaning by removing any message handlers and deallocating scriptHelper to avoid memory leaks.

  1. override func viewWillDisappear(_ animated: Bool)
  2. {
  3.   super.viewWillDisappear(animated)
  4.   guard let sh = scriptHelper else { return }
  5.   // Clear message handler from script helper
  6.   webView.configuration.userContentController.removeScriptMessageHandler(
  7.       forName: sh.webObserverName)
  8.   scriptHelper = nil
  9. }

That's all I need to do to set my gateway up. First, the gateway will handle a message requesting the app version from the JS side. Then it will call a JS function to pass the app version to JS.

HTML and JS Configuration

On the html and JS side, the first step is to make a request to Swift for the app version. This can be done by calling a JS function on html body element's onload attribute.

  1. <body onload="updateVersion()">
  2. ...
  3. <h2 class="center">Version
  4. <span id="appVersion">to be updated...</span></h2>
  5. ...
  6. </body>

The second step is to set up Javascript functions to make a request and to handle Swift calls.

  1. function updateVersion()
  2. {
  3.  try {
  4.    var msg = {}; // for empty parameter, it has to be {}
  5.    // "appVersion" is the request message name on Swift side
  6.    window.webkit.messageHandlers.appVersion.postMessage(msg);
  7.  } catch(err) {}
  8. }
  9. // function to be called from Swift to pass app version
  10. // to JS.
  11. function swift2JSCallFor(appVersion) {
  12.  let e = document.getElementById("appVersion");
  13.  e.innerHTML = appVersion;
  14. }

The comment description in the Javascript explains everything. That's all. With the above configuration, Swift and JS can talk to each other and pass the required information as needed.

Here is a snapshot of my html view with the correct version updated. I am so happy that this update will be automatically done and I don't need to touch the html file in future updates.

I have a short summary at the conclusion of this blog. Swift-JS communication was new to me. I spent more than one week investigating and doing my tests. The above strategy is based on other resources (see references) and my effort. This is not the best and complete solution. I may miss something. Please leave comments and provide your better solutions. Critics are welcome.

References

Read More...

Tuesday, August 02, 2022

HTML Head Settings

Recently, I have been busy working on my iOS updates. One feature I want to add is to provide some information views in the app.

There are many ways to present information on display in an app. I could use a rich-text component or a text view with attributed strings. However, compared to HTML, I prefer to use a web viewer to display HTML content. The most recent update and recommended strategy by Apple is WKWebView. This component fully supports HTML5 features with extensive Apple support for iOS devices, such as dynamic text size, zooming and pinch support, accessibility support, and more.

ViewController With WKWebView

With the HTML strategy, I could use HTML to format my information content with css styles, images, and js scripts. The way it works is just like a web server/client fully functional environment. My project acts like a web server. I place all my HTML files, images, css files, and js files into groups. Add a view controller class with WKWebView as the main container.

  1. class MyHTML1ViewController: UIViewController {
  2.   private var webView: WKWebView!
  3.   var htmlFile: String = "" // default value, can be changed
  4.   override func viewDidLoad() {
  5.    super.viewDidLoad()
  6.    let url = Bundle.main.url(
  7.            forResource: htmlFile,
  8.            withExtension: "html")
  9.    let request = URLRequest(url: url)
  10.    webViewBase.load(request)
  11.  }
  12. ...
  13. }

Then an html file can be loaded into the view, and the content is displayed in a way similar to a web browser.

Localization

In my project, Xcode provides support for localization. All HTML files, image files, and css files can be localised based on language support in my project. Currently, I have 3 languages to support, plus a base. A total of 4 sets of files are automatically created for me in my project. Taking an HTML file as an example, 4 HTML files are created for each language, and they are placed into a subfolder named by its language identifier. The same is true for images and css files.

Here is a snapshot of my project for groups of HTML-supported files. I group them into js, css, images, and html group names.

As you can see, each localizable item has a list of supported languages. With this visual layout, I can easily pick a language file and do my translation work.

The file structure is very different from the visual view of the project. Actually, all files are in the same root folder of projects. It is really hard to find them if I want to open Finder to find a file. That's why I named all HTML-supported files with a prefix of html_.

Further investigation, I found that within my app main bundle, all HTML supported localizable files within the project are placed in subfolders by the name of language identifiers. In other words, in my app bundle, all localised files are placed in a subfolder named as the language identifier. For example, for English, "en.lproj" subfolder contains all localised files, including my HTML-supported files. One level up from language subfolders is the root path, where all none-localized files sit. For example, js files are at the root level.

As I present all this in the above pictures, I will try to explain my understanding of how web server/client works within my app project. This picture of file layout is the web server's path structure. When an html file is loaded into WKWebViewer, all related images and css files are in the same path as the html file. Up one level is the place where js files are located.

With this understanding, I can edit my html files to refer to other source files and components easily. WKWebView is something like a browser to present HTML content and let users review and interact with it. As a client, the viewer has knowledge about where to get those sources and components from my project. I call it a server.

Keep in mind, I integrated WKWebView views into my project is based on HTML support and functionality. However, my app, built by my project, does not provide any end points for external web clients to access. My app is a native iOS app running on an iOS device.

Duplication of Settings in the head Section

I have several HMTL files to be presented by a number of view controllers with WKWebView, currently about, information and setttings. There will be expanded to more, let's say n HTML contents. Four language sets are supported in my project, and might be increased to more, m languages supported. You can imagine that n x m HTML files are in my project.

One thing I realise is that within an HTML file, in the document.head section, there are many settings. For example, common and specific css source files, common and specific js libray files, and meta configuration for various settings such as view point. Some of them are the same or repeated in all HTML files.

I realised there would be a maintenance issue. If any of these common parts are changed, as I will find some interesting feature support, I have to edit all those HTML files to do just copy and paste work. Is there any better way to do the maintenance job? Actually, I had encountered some issues where I forgot to update some languages, and those language displays did not work in the way I expected.

Seeking Help from SO

StackOverflow is a community of IT programmers' participation network. There are many talented people there to support others with all kinds of questions. After struggling to find a solution, I posted my question there. I was hoping to get advice from talented people.

Before I posted my Q, I tried various ways, including using Swift within my project. There is one way to open an HMTL url from my project. Then I could do parameter replacement of the content of the HTML file as a string. However, I remembered that I tried this method before. I just could not change any files within my application bundle!

The short story is that I tried to use a plist file within my project to save some setting changes. No errors are thrown out when some new changes are saved to the plist file. However, when the settings were read out later, they were back to their original values. I had been struggling for days to find out why, and finally I found that the plist file in the project bondle cannot be changed!

I resolved the issue by copying the plist file to the application sandbox supported folder. The plist file can be edited and saved there. After that, I come to my explanation: it may be a security reason that files within the app bundle cannot be changed.

I could copy the HTML file to outside of the bondle path and make changes there. What about other HTML sources and library files? It just defeats the language support naturally provided by the iOS framework if I have to find out or identify which language is supported, and then copy all those files from specific language subfolders out.

I knew there is another way to load an HTML string directly into WKWebView. The string can be changed with a parameter replacement. This would display HTML content. However, the same issue comes back to me again. How about linking other sources and library files if I load the view with a string? No path information from the string

Since I am using HTML technology, naturally I should find a way in HTML or js to inject common head settings into my loading HTML file.

Partial Working Solution

Within about a day of my post, I got two answers. One is using js to inject common settings from a js function. I tried it immediately. It did not work at first.

When I went back to my Q, my Q was marked as [losed] by a senior member of SO. The reason is that my working environment was not explained clearly. More explanation is required.

With [losed] status, no more answers are allowed. This actually turns many people away. The only way I could ask for help and get attention was to update my Q and add comments. As you can see, I have done several updates, and the Q is quite lengthy. Anyway, I think I have done enough to clarify my Q on SO.

If you read my Q, you will see a solution in it. Based on the only post's answer with codes, I made some changes, and took the meta setting for charset out of my common settings. It works now in my project, just as I expected, to inject the common settings into the head section.

Even though my solution is working, I still have questions about it, and I would like to see if there is any other better solution or advice on this issue.

Since SO is a member participation and support community, I need to get some people's help to turn off the [close] status. What is required is to have 2 more requests at the link of reopen, at the end of my Q.

References

Read More...

Thursday, March 21, 2013

Use HTML Button Tag in Blog

This morning I read an article on web. The article is a long serials one, but it does not show the whole article on one page. Instead, it breaks down into small ones and placed serval buttons at the end of each one: First, 1, 2, ..., Last. I like this style. I immediately related this to my blog. Sometimes, I do the same thing, but I use UL LI list tags to show them as a list.

I did some investigation. What I found are three basic ones. First is to use DIV or UL LI with CSS. Although it does provides flexible and nice layout, it is a little bit complicated. The most concern I have is that I have to define CSS styles in my blog headers. This is not an issue in Blogger, but my other blog web providers do not allow me to add CSS styles.

The second method is INPUT tag. It is very simple. It provide a button like UI. However, it is a single tag with limited layout enhancement.

The third one is BUTTON tag. With this tag, any HTML tags can be placed within open and close tags. I like this one very much. Both INPUT and BUTTON utilize OnClick event to trigger click or hit event. For my blog purpose, what I need is to direct to another URL, either in current document or a new window. This can be done by client side JavaScript. I'll show this later on.

Here I give it a try in this blog.

The following codes define two BUTTON tags. By using inline Javascript, one will open a URL (Apple web page) in another new window and another open Microsoft web page in the current window. Within each button, there is an image content with text on the next line.

<button type="button"
 onclick="JavaScript:window.open('http://www.apple.com')">
 <img src="http://images.apple.com/home/images/billboard_ipad_hero.jpg" />
 <br/>Go to Apple... make my day!
</button>
<button type="button"
 onclick="JavaScript:document.location='http://www.microsoft.com';">
 <img src="http://compass.surface.com/assets/e4/38/e43802e2-8e9c-4fb9-80d6-f27fa6cf50df.jpg#1280_carousel_1_meet_surface.jpg" />
 <br/>Go to Microsoft... make my yesterday!
</button>

Here is another example with two buttons. I added a SPAN tag with TITLE attribute so that each button has a tooltip when you move mouse over the button.

<button onclick="JavaScript:window.open('http://davidchuprogramming.blogspot.com/2004/05/my-first-blog-entry.html')" type="button">
<span title="First blog">||&lt;&lt;</span></button>

<button onclick="JavaScript:document.location='http://davidchuprogramming.blogspot.com/2013/02/communication-between-mvc-controllers.html';" type="button">
<span title="Previous blog">&lt;</span>
</button>

Read More...

Friday, July 01, 2011

No Overflow for a Simple Math Calculation

I watched WebBeat podcast one day in the last month. One video show got my attention: Explaining visual math in a simple way. I wrote a blog on my Chinese blog. It is very interesting to resolve a math multiply by visualization graphs. However, this method only applies to multiply of 2 digit numbers, and it becomes tedious when there carrier forward in the last and middle numbers.

This reminds me a long time puzzle problem: how to resolve overflow issue when the result of multiply of two integers is too big. I recall the traditional method of multiply algorithm and relate it to this visualization. Soon I realize that I could resolve the overflow issue by creating a small app. I gave it a try with Javascript and I got it worked out! I am very happy with the result. No more overflow no matter how big numbers they are!

Here is my Javascript function:

function v1timesv2(v1, v2) {
  var x = ''; // result
  var i = 0;
  var j = 0;
  var r;
  var r1;
  var r2;
  var k;
  var tempVal;
  var preTempVal = '';
  debug ('v1: ' + v1);
  debug ('v2: ' + v2);
  for (i= v2.length-1; i >= 0; i--) // v2
  {
    r2 = 0;
    tempVal = '';
    k = preTempVal.length;
    for (j=v1.length-1;j >=0; j--)  // v1
    {
      if ((k--) > 0)
      {
        r2 += Number(preTempVal[k]);
      }
      r = v1[j] * v2[i] + r2;
      r1 = r % 10;
      r2 = 0;
      if ( r > 9 )
      {
        r2 = (r - r1) / 10;
      }
      tempVal = r1 + tempVal;
    }
    if ( r > 9 )
    {
       tempVal = r2 + tempVal;
    }
    debug('intermediate calculated result: ' + tempVal);
    // Get the last char as result
    x = tempVal[tempVal.length - 1] + x;
    debug('intermediate result: ' + x);
    // Get the remaining as previous val
    if ( tempVal.length > 1 )
    {
      preTempVal = tempVal.substr(0, tempVal.length - 1);
    }
    else
    {
      preTempVal= '';
    }
    debug('== carry forward result: ' + preTempVal + ' ==' );
  }
  x = preTempVal + x;
  debug('>>final result:    ' + x);
  debug('>>verified result: ' + Number(v1) * Number(v2) + ' (' + v1 + ' x ' + v2 + ')');
  return x ;
}


This may not be the best codes. Please let me know if you can simplify my codes. Download my numberCalc.html and drop it to a browser. I challenge if you can break my codes with an overflow!


References


Read More...

Saturday, June 25, 2011

CoffeeScript and .Net Nancy Framework

Last week when I listened to my podcasts, two projects got my attention. The firs one is from HerdingCode podcast 114 about: Trevor Burnham on CoffeeScript. This is light language in clean syntax that can be compiled into JavaScripts, in one to one relationship.

The second one is from HansellMinutes on 6/16/2011: Nancy, Sinatra and Exposion of .Net Micro Web Framewor. The Nancy project was introduced on Nov 28th, 2010. The blog site Elegent Code posted a blog about this podcast as well on Jun 28, 2011.

Read More...

Friday, June 17, 2011

Edit Blogger HTML Settings

This is blog is technical detail I did in 2007. I wrote this technique long time ago. It is truth that this is only required to do only once. However, when I create another blogger account, I would like to repeat it again. Now I think it is better to take detail notes of steps so that I may need to reuse it again.

Here is the example I edited HTML for my Chinese blog.

Backup HTML


It is important to backup your Blogger HTML settings before doing any updates. Here is the place to find the HTML settings and make the complete copy of HTML before any updates.



Copy My CSS Settings


The following CSS codes are used by my blog. Insert the CSS definitions into header section. Here I copied it to end of </b:skin>

<b:skin><![CDATA[/*-----------------------------------------------
...
/* David Inserted styles */
/* Use these styles as class ID for a box for some blocks such as div, p */
.outlinebox {
  width: 435px;
  border-width: 1px;        /* sets border width on all sides */
  border-style: solid;
  border-color: #666666;
  padding: 5px
  }

.outlineterminalbox {
  width: 850px;
  border-width: 1px;        /* sets border width on all sides */
  border-style: solid;
  border-color: #C0C0C0;
  font-family:monospace, prestige;
  background-color: #080808;
  color: #FFFF00;
  padding: 5px
  }

.outlinebox4wrappercodes {
  width: 444px;
  border-width: 1px;        /* sets border width on all sides */
  border-style: solid;
  border-color: #666666;
  margin: 0px auto;
  }

.outlineboxterminal4wrappercodes {
  width: 444px;
  border-width: 1px;        /* sets border width on all sides */
  border-style: solid;
  border-color: #080808;
  color: yellow;
  margin: 0px auto;
  }

/* code settings with following 2 outline Withbackclr. Generated in VIM TOhtml cmd */
.Type { color: #60ff60; }
.Comment { color: #80a0ff; }
.Identifier { color: #40ffff; }
.PreProc { color: #ff80ff; }
.Constant { color: #ffa0a0; }
.Statement { color: #fbf204; }
.Special { color: #fb8000; }

.outlinebox4wrappercodesWithbackclr {
  width: 444px;
  border-width: 1px;        /* sets border width on all sides */
  border-style: solid;
  border-color: #666666;
  margin: 0px auto;
  background-color: #000000;
  color: #cccccc;
  }

.outlineboxterminal4wrappercodesWithbackclr {
  width: 444px;
  border-width: 1px;        /* sets border width on all sides */
  border-style: solid;
  border-color: #080808;
  color: yellow;
  margin: 0px auto;
  background-color: #000000;
  color: #cccccc;
  }
 
.wrappercodes {
  width: 435px;
  padding: 5px;
  overflow: auto;
  }
 
.wrappercodesWithHeight {
  width: 435px;
  height: 300px;
  padding: 5px;
  overflow: auto;
  }

.wrappercodesWithHeightNowrap {
  width: 435px;
  height: 300px;
  padding: 5px;
  overflow: auto;
  white-space:nowrap;
  }

.nowrappercodes {
  width: 435px;
  padding: 5px;
  overflow: auto;
  white-space:nowrap;
  }

.nowrappercodesinner {
  width: auto;
  padding: 5px;
  overflow: auto;
  white-space:nowrap;
  }

.nowrappercodesWithHeight {
  width: 435px;
  height: 300px;
  padding: 5px;
  overflow: auto;
  white-space:nowrap;
  }
 
/* code style */
span.kwrd {
  color: rbg(0, 0, 255);
  }

span.defType {
  color: rgb(43, 145, 175);
  }
 
span.errorcode {
  color: rbg(255, 0, 0);
  }
 
span.highlight
{
color: white;
background-color: rgb(0, 100, 200);
}

/* Code Colors copied ccs from http://forums.asp.net/utility/colorcode.css for code colors */
/* Use asp.net forums on-line post to generate codes with these ccs class */

.coloredcode .cmt      { color: #00D502; }
.coloredcode .cmtg      { color: #666; }
.coloredcode .st           { color: #f00; }
.coloredcode .kwdt      { color: #666; }
.coloredcode .kwd      { color: #00f; }
.coloredcode .attr      { color: #f00; }
.coloredcode .attrv      { color: #00f; }
.coloredcode .ec           { color: #00f; }
.coloredcode .tag      { color: #800000; }
.coloredcode .dir    { color: #000; background: #FEFF22; }
.coloredcode .sel    { color: #800000; }
.coloredcode .val    { color: #00f; }
.coloredcode .unit      { color: #800000; }
.coloredcode .sqlcmt { color: #008080; }
.coloredcode .sqlkwd { color: #00f; }
.coloredcode .sqlkwd2 { color: #f0f; }
.coloredcode .sqlkwd3 { color: #999; }
.coloredcode .sqlkwd4 { color: #800000; }
.coloredcode .num      { color: #00f; }
.coloredcode .sqlst  { color: #008000;}
.coloredcode .classInterface { color: #1E90FF;}
.coloredcode b      { font-weight: normal; color: #008080; }
.coloredcode          { font-size: small !important; }
.row-set-descriptor {
  border-bottom: 1px solid #F60;
  border-bottom-style: solid;
  border-bottom-width: 1px;
  border-bottom-color: #F60;
  clear: left;
  color: #E55C00;
  font-size: 140%;
  margin: 0px 20px 0.5em 0px;
  margin-top: 0px;
  margin-right: 20px;
  margin-bottom: 0.5em;
  margin-left: 0px;
  padding: 1em 0px 0.2em 0em;
  padding-top: 1em;
  padding-right: 0px;
  padding-bottom: 
}

 /* End of Code Colors */
 /* End of David inserted styles */

]] > </b:skin>


Copy Scripts


Immediately after about </b:skin>, add Javascript codes.

<b:skin>
  ...
]] > </b:skin>
<script type='text/javascript'>
function toggleIt(id) {
  var post = document.getElementById(id);
  if (post.style.display != &#39;none&#39;) {
    post.style.display = &#39;none&#39;;
  }
  else {
    post.style.display = &#39;&#39;;
  }
}

function showFullPost(id) {
  var post = document.getElementById(id);
  var spans = post.getElementsByTagName(&#39;span&#39;);
  for (var i = 0; i != spans.length; i++) {
    if (spans[i].id == &quot;fullpost&quot;)
      spans[i].style.display = &#39;inline&#39;;
    if (spans[i].id == &quot;readmore&quot;)
      spans[i].style.display = &#39;none&#39;;
  }
}

function showFull(id) {
  var post = document.getElementById(id);
  var spans = post.getElementsByTagName(&#39;span&#39;);
  for (var i = 0; i != spans.length; i++) {
    if (spans[i].id == &quot;fullpost&quot;)
    spans[i].style.display = &#39;inline&#39;;
    if (spans[i].id == &quot;showlink&quot;)
      spans[i].style.display = &#39;none&#39;;
    if (spans[i].id == &quot;hidelink&quot;)
      spans[i].style.display = &#39;inline&#39;;
  }
}

function hideFull(id) {
  var post = document.getElementById(id);
  var spans = post.getElementsByTagName(&#39;span&#39;);
  for (var i = 0; i != spans.length; i++) {
    if (spans[i].id == &quot;fullpost&quot;)
      spans[i].style.display = &#39;none&#39;;
    if (spans[i].id == &quot;showlink&quot;)
      spans[i].style.display = &#39;inline&#39;;
    if (spans[i].id == &quot;hidelink&quot;)
      spans[i].style.display = &#39;none&#39;;
  }
  post.scrollIntoView(true);
}

function checkFull(id) {
  var post = document.getElementById(id);
  var spans = post.getElementsByTagName(&#39;span&#39;);
  var found = 0;
  for (var i = 0; i != spans.length; i++) {
    if (spans[i].id == &quot;fullpost&quot;)
      found = 1;
    if (spans[i].id == &quot;showlink&quot;)
      if (found == 0)
        spans[i].style.display = &#39;none&#39;;
  }
}
</script>



Insert Script Function Calls in Body Section


MODIFYING BLOG POSTS CODES

First find the line: <div class='post-header-line-1'/>

Insert the following codes after it:

<div class='post-body'>
  <b:if cond='data:blog.pageType == &quot;item&quot;'>
    <p><data:post.body/></p>
    <div style='clear: both;'/>
  <b:else/>
    <style>#fullpost {display:none;}</style>
    <p><data:post.body/></p>
    <span id='showlink'>
      <p>
        <a expr:onclick='&quot;javascript:showFull(\&quot;post-&quot; + data:post.id + &quot;\&quot;);&quot;'
        href='javascript:void(0);'>读详细内容(Read More)...</a>
      </p>
    </span>
    <span id='hidelink' style='display:none'>
      <p>
        <a expr:onclick='&quot;javascript:hideFull(\&quot;post-&quot; + data:post.id + &quot;\&quot;);&quot;'
        href='javascript:void(0);'>Collapse...</a>
      </p>
    </span>
    <script type='text/javascript'>
      checkFull(&quot;post-&quot; + &quot;<data:post.id/>&quot;);
    </script>
  </b:if>
</div>



NOTE: I found there is a duplicate post when the title of the post is clicked. The reason is that there is another div after the above codes:

<div class='post-body entry-content' expr:id='&quot;post-body-&quot; + data:post.id'>
  <data:post.body/>
  <div style='clear: both;'/> <!-- clear for photos floats -->
</div>



delete this section of div to avoid duplications.

NOTE: remove two tags: <div class="post-header"> ... </div> to avoid the content of post content in grey.

CODE FOR THE INCLUDABLE

Find the code: <b:includable id='feedLinksBody' var='links'>
Insert the following codes above it:

<b:includable id='PeekABooPost' var='post'>
  <div class='post uncustomized-post-template'>
    <table><tbody>
      <tr><td>
        <h3 class='post-title'><a expr:href='data:post.url'
        title='View this post only (comments visible).'><data:post.title/></a></h3>
      </td></tr>
      <tr><td>
        <a expr:onclick='&quot;javascript:toggleIt(\&quot;&quot; + data:post.id + &quot;\&quot;);&quot;'
          href='javascript:void(0)'
          style='text-decoration:none'
          title='Expand/collapse this post (comments not visible)'>[+/-] 显示内容(Show/Hide This Post).</a><br/>
        <a expr:href='data:post.url' target='_blank'>
          <i><font size='-2'>See/leave comments for this post in another window/tab.</font></i></a>
      </td></tr>
    </tbody></table>
    <div class='post-body' expr:id='data:post.id' style='display:none'>
       <p><data:post.body/></p>
       <div style='clear: both;'/>
    </div>
  </div>
</b:includable>
<b:includable id='feedLinksBoday' var='links'>



INCLUDE THE INCLUDABLE IN THE POSTS WIDGET

Find the code: <b:includable id='main' var='top'>
In this section find: <b:include data='post' name='post'/>
Change it to:

<b:if cond='data:blog.homepageUrl == data:blog.url'>
  <b:if cond='data:blog.pageType != &quot;item&quot;'>
    <b:include data='post' name='PeekABooPost'/>
  <b:else/>
    <b:include data='post' name='post'/>
  </b:if>
<b:else/>
  <b:include data='post' name='post'/>
</b:if>



Copy HTML Back to Blogger HTML Settings


After the change, copy the content of HTML from your local file back to blogger HTML settings.

REFERENCE

This feature of shrink and expend bloggs is based on this blog.

Read More...

Monday, April 13, 2009

Yahoo YUI Reading Blinds

Yahoo YUI Reading Blinds is a very nice browser tool based on JavaScript and YUI. Recently I tried to dig out more about this tool by looking its codes. Here are some of my findings.

First, the tool is based on a group of JavaScript codes. It is very long one like this:

javascript:var%20x=%20function(){var%20h=document.createElement('script');h.src='http://yuiblog.com/assets/readingblinds.js';h.type='text/javascript';document.getElementsByTagName('head')[0].appendChild(h)}();


This long line of codes does not have spaces. The spaces are actually specified by "%20" in codes. Then I break the codes in a nice format like this:

javascript:var x= function(){
var h=document.createElement('script');
h.src='http://yuiblog.com/assets/readingblinds.js';
h.type='text/javascript';
document.getElementsByTagName('head')[0].appendChild(h)
}();


In stead of putting this codes to toolbar, I added this tool as a new bookmark to my bookmark (I am using Vimperator and there is no toolbar visible). From the bookmark, I click on this. It works fine with reading blinds visible on top and bottom, very nice! I like this reading blinds more than others (you may find other reading blinds on web). It works well with with mouse movement and vertical scroll bar.



The only problem I had was that my Vimperator caches all the keystrokes. I could not use s, l, and b keys for smaller, larger and toggle. Vimperator blockes them to the tool. Fortunately, Vimperator has a "PASS THOUGH" mode to let all the keys passed: Control-Z.

This tool actually injects YUI JavaScript codes into the page: loading YUI scripts into <head> area. The scripts then adds top and bottom <div>s as blinds. In addition to that, it adds key and mouse event to move blinds.

I further hacker this tool to change the URL to the script src to my local Mac's Sites folder like this:

...
h.src='http://localhost/~[username]/YahooYUIReadingBlinds/readingblinds.js';
...


I added this to another bookmark as "YUI Reading Blinds Local Scripts". It works great! Since I download the readingblinds.js to my local site, I then made some changes to figure out how it works and to test its features with different settings. It was really fun to work with this tool.

The reading blinds will become darker if you click on the tool more than once. With FireBug enabled, I noticed that the scripts are injected more than once.

To get rid of this reading blinds, just refresh the web page. The original web page is reloaded and the JavaScript inject is gone.

The reading blinds does not work with https web URLs since https URL is an encrypted HTML protocol and no JavaScript injection are allowed.

This tool is a very good example to enhance web page display by using JavaScript injection!

By the way, I feel much easy to read web pages and my eyes are more relaxed with this great tool for a long period of time, especially with the browser maximized. I think this may protect screen as well.

Read More...

Sunday, March 29, 2009

Dojo Chart Library

I read an article about Dojo Now With Chart Tools by Mattew Russell, the author of the book Dojo The Definitive Guide by O'Reilly.

The article lists several demos on generating charting by Dojox.charting library. I then started to copy the codes to an HTML file. Instead of loading Dojo libraries to my local computer even I have a personal web site on my Mac, I used xDomain reference to Google's Dojo CDN library. In this way, I don't need any web server and I can send this demo HTML to my colleagues to share it. My colleagues at my work really enjoyed it.

This demo does not only lists original JS codes. I enhanced the charts with different CSS styles and colors. It is so easy to do that. In addition to that, I allocate an 600x600 area at top as an area for chart and list my demos a UL list underneath it. This arrangement needs to clean the area for any chart previously displayed. The Dodo library does not provide APIs to clean chart. I finally found a way to clean it: simply removing all the childen nodes in the area. Even this one works, but it is not recommended. An Dojo insider pointed out this method does not clean all the objects created for the chart. I posted my question to StackOverflow and Dojo's forum.

My previous blog also mentioned the issue about xDomain reference. All these were really challenges for me since I am new in Dojo. I have not done much development in Dojo. However, the demo and enhancements let me learn a lots.

Here is my demo on Google's Code.

Read More...

Saturday, March 28, 2009

FireBug and xDomain Reference for JS APIs

Last week I read a very interesting article on Dojo's charting library. There are some demo codes in the web page. Instead of downloading Dojo's library to my local computer web site, I choose to use CDN fashion to load Dojo library from Google's CDN. This method is called Cross Domain Resource Reference or xDomain Reference.

In this way, I don't even need a web server. I just use VI to write an HTML file with JavaScript and Dojo API calls. That's the basic of web application: HTML + JS. I dropped the HTML file to my browser (FireFox) and it worked right away.

However, for the same codes which I found them in Dojo's ToolKit web page, one of feature does not work. It is the animation of curve chart. This feature is added by calling Magnify() API function to the chart. Behind sense, animation parts are created to each point. In the Dojo's web page, the magnification works fine but not in my HTML.

I spent about one day's time finally I figured it out. What I missed is to add the following codes to the head section of HMLT:

<head>
...
<script type="text/javascript">
...
dojo.require("dojo.fx");
...
</script>
</head>


FirBug helped me to find this one out. When I checked my HTML source codes in FirBug, the script loading section shows the loaded source codes in FireBug window. However, for the xDomain reference HTML page, the source codes are partially and they are displayed as one very very long line like this:


As you can see how hard to read when I copied the codes to VI:



Fortunately, the same codes are available on DojoToolKit web page, where Dojo's library files are not xDomain references. They are in the same domain. By using FireBug to take a peek inside the web page, I found the Magnify.js source codes are in nice layout:



By the way, I also tried to get source codes directly from Google's CDN web site, for example, Dojo.xd.js. It also displayed as a very very long line. But I went to DojoToolKit's web page to load the source code from their domain, I was prompt to save or open js. The download js file is in nice layout format. Here it reveals that CDN is not only for fast downloading, the size of the file is also very small. All the unnecessary codes such as line breaks are removed!

Read More...