Joho the Blogjavascript Archives - Joho the Blog

August 4, 2015

Posting to WordPress without WordPress

Perhaps you’d like to post to your WordPress blog from an app that isn’t WordPress.

I know I do. I write most of my posts in an editor (javascript + php) I’ve worked on for over ten years. Someday I’ll clean it up and post it at GitHub so you can all have a laugh. Meanwhile, it intermittently loses its ability to post straight to my blog, so I have to copy and paste the text into the WP editor. But I fixed it again today. So, here’s a tutorial for people at my level of non-technicality. (I got huge help from a post at HurricaneSoftware. Thanks!)

First, make sure that the file xmlrpc.php is installed where you’ve installed your WordPress blog software. This file comes from WordPress itself, and it should be there automatically. Check the permissions; I think it should be 644 but I am terrible at permissions.

I run my homegrown editor from my Mac, using the Apache web server that MAMP supplies. That lets me write blog posts even when I’m not online. That means the directory from which I’m running my JavaScript and PHP is on my hard drive. I keep these files in  /Applications/MAMP/htdocs/blogdraft/. (Blogdraft is the name of the folder in which my code resides.) To the web server, the address looks like this: /localhost/blogdraft/.

The operative part of this is your PHP file. Create an empty text file and name it, let’s say, postViaXmlrcp.php. For a first pass, it should look like this—and the brunt of this comes straight from HurricaneSoftware:

1

<?php

 
2

// Modified from:

3

// http://www.hurricanesoftwares.com/wordpress-xmlrpc-posting-content-from-outside-wordpress-admin-panel/

4

// Thanks!!

 

5

require_once(“IXR_Library.php.inc”);

6

$client->debug = true; //Set it to false in Production Environment

 
7

$title=$_REQUEST[‘title’];

8

$body=$_REQUEST[‘body’];

9

$keywords=$_REQUEST[‘tags’];

10

$category=$_REQUEST[‘categoryArray’];

11

$customfields=null;

 

12

$encoding = ini_get(“default_charset”);

13

$title = htmlentities($title,ENT_QUOTES,$encoding);

14

$keywords = htmlentities($keywords,ENT_QUOTES,$encoding);

 

15

$content = array(

16

‘title’=>$title,

17

‘description’=>$body,

18

‘mt_allow_comments’=>1, // 1 to allow comments

19

‘mt_allow_pings’=>1, // 1 to allow trackbacks

20

‘post_type’=>’post’,

21

‘mt_keywords’=>$keywords,

22

‘categories’=>$category,

23

‘custom_fields’ => array($customfields)

24

);

 

25

// Create the client object

26

$client = new IXR_Client(‘http://www.yourblog.com/myWP/xmlrpc.php’);

27

$username = “your-WP-username”;

28

$password = “your-WP-password”;

 

29

$params = array(0,$username,$password,$content,true); // Last parameter is ‘true’ which means post immediately, to save as draft set it as ‘false’

 

30

// Run a query for PHP

31

if (!$client->query(‘metaWeblog.newPost’, $params)) {

32

die(‘Something went wrong – ‘.$client->getErrorCode().’ : ‘.$client->getErrorMessage());

33

} else { echo “Article Posted Successfully”; }

34

?>

This PHP script relies upon another one, so you have to load it. “Require_once” will do so, and it will remember that it has done so during a session so you won’t waste computer resources reloading it every time you run this script.

You can get this script here. Right click on that link and choose “Save file as…” or however your browser puts it. Put it in the same directory as your PHP script. Make sure you name it “IXR_Librabry.php.inc.” Set its permissions. (See above.) Then leave it alone. 

7

$title=$_REQUEST[‘title’];

8

$body=$_REQUEST[‘body’];

9

$keywords=$_REQUEST[‘tags’];

10

$category=$_REQUEST[‘categoryArray’];

11

$customfields=null;

These lines read data that you’ve sent from the JavaScript that we haven’t written yet. It assigns them to some pretty obviously-named PHP variables.

Notice that we’re doing nothing with the $customfields variable. That’s because I don’t know what to do with it. I would have just deleted that line, but it scares me. And yet fascinates me.

12

$encoding = ini_get(“default_charset”);

13

$title = htmlentities($title,ENT_QUOTES,$encoding);

14

$keywords = htmlentities($keywords,ENT_QUOTES,$encoding);

htmlentities is a PHP function that makes sure that your HTML with all of its weird characters arrive without being translated into something more literal and wrong. Line 12 tells it which character encoding to use. I could have decided on one for you, but instead I’m just using whichever one you already use. We have already established I’m a coward, right?

15

$content = array(

16

‘title’=>$title,

17

‘description’=>$body,

18

‘mt_allow_comments’=>1, // 1 to allow comments

19

‘mt_allow_pings’=>1, // 1 to allow trackbacks

20

‘post_type’=>’post’,

21

‘mt_keywords’=>$keywords,

22

‘categories’=>$category,

23

‘custom_fields’ => array($customfields)

24

);

We are eventually going to be sending all of the content information to  WordPress via XMLRPC. This section packs an array (“$content”) with the information XMLRPC needs, attached to the keywords it loves. If you want to argue about it, take it up with XMLRPC.

26

$client = new IXR_Client(‘http://www.yourblog.com/myWP/xmlrpc.php’);

We now create a new client for the IXR script you downloaded. It wants to know where your xmlrpc.php file is, which should be where the rest of your WordPress files are folders are.

27

$username = “your-WP-username”;

28

$password = “your-WP-password”;

Fill in your WordPress username and password.

29

$params = array(0,$username,$password,$content,true); // Last parameter is ‘true’ which means post immediately, to save as draft set it as ‘false’

Now we’re making another array. This one includes the prior array ($content) as well as your username and password. And note the comment. Setting to “draft” is very useful when you’re playing around with these scripts.

31

if (!$client->query(‘metaWeblog.newPost’, $params)) {

32

die(‘Something went wrong – ‘.$client->getErrorCode().’ : ‘.$client->getErrorMessage());

33

} else { echo “Article Posted Successfully”; } ?>

This does the deed. No one knows how.  If it fails, it’ll pop up the error messages and kill it before it spawns evil. Otherwise, it sends back the message that it posted successfully.

You can test this PHP script by running it in your browser. If you’re running a local web server, you’d enter something like this: /localhost/blogdraft/postViaXmlrcp.php. (That’s assuming you put it in a folder called “blogdraft,” of course.) Check with MAMP or whatever you’re using for your web server for details.

But running this as-is won’t work because it’s expecting the content to be sent to it from the JavaScript we still haven’t written. So, comment out lines #7-11, and insert something like these:

$title=”TEST TITLE”;

$body=”<h1>Hello, world!</h1>”

$keywords=”tag1,tag2″

$category=array(“cat1″,”cat2″);

$customfields=null;

Replace the categories (“cat1″, “cat2″) with the names of categories that you actually use. Also, change “true” to “false” in line #29 so you’ll just produce drafts, not actually publish anything yet.

Now when you run this PHP file in your browser ( /localhost/yourLocalFolder/postViaXmlrcp.php), if should create a draft post. Check via the “All posts” page at your WP administration page to see if the draft got created.

When it’s working, comment out the four lines immediately above and uncomment lines #7-11.

The JavaScript

I’m going to pretend that you have some HTML page that has a text box where you can enter the content of your post, a similar box for entering the title, one for entering tags separated by commas, and checkboxes that list the categories you use. I’ll also assume that you use jQuery. So, your HTML might look soomething like this:

1

<!DOCTYPE html>

2

<html lang=”en”>

3

<head>

4

<meta charset=”utf-8″ />

5

<title>WordPress poster tester</title>

6

<script src=”https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js”></script>

 

7

<script>

8

function postIt(){

9

// get the title

10

var titlecontent = $(“#titlebox”).val();

11

// get the body of the post

12

var bodycontent = $(“#contentbox”).val();

13

// get the tags

14

var tagscontent = $(“#tagsbox”).val();

 

15

// create array of categories

16

// get an array of all checkboxes in the div

17

var checks = $(“#categories”).find(“input”);

18

// create an empty array

19

var cats = new Array();

20

// go through all the checkboxes

21

for (var i=0; i < checks.length; i++){

22

// is this one checked?

23

if ($(checks[i]).is(‘:checked’)){

24

// if so, then push its value into the array

25

cats.push( $(checks[i]).val() );

26

}

27

}

 
28

// run the php

29

$.ajax({

30

type: “POST”,

31

url: “postViaXmlrcp.php”,

32

dataType: JSON,

33

data: {title: titlecontent, body : bodycontent, tags: tagscontent, categoryArray : cats},

34

error: function(e){

35

if (e.responseText.indexOf(“Successfully”) > -1){

36

alert(“Success! Post has been posted! Let the regrets begin!”);

37

}

38

else{

39

alert(‘Error posting blog via xmlrpc: ‘ + e.responseText);

40

}

41

}

42

})

 
43

}

 
44

</script>

 

 

45

</head>

46

<body>

47

<textarea id=”titlebox”>test title</textarea>

48

<textarea id=”contentbox”><h1>got some content here</h1></textarea>

49

<textarea id=”tagsbox”>tag1, tag2</textarea>

 
50

<div id=”categories”>

51

<input value=”business” type=”checkbox” checked>Cats

52

<input value=”dogs” type=”checkbox”>Dogs

53

<input value=”philosophy” type=”checkbox”>Phenomenology

54

</div>

 
55

<input type=”button” value=”Post It!” onclick=”postIt()”>

56

</body>

57

</html>

 

So, roughly, here’s what’s happening:

6

<script src=”https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js”></script>

This loads jQuery from Google. Of course you could keep a local copy and include it that way.

8

function postIt(){

9

// get the title

10

var titlecontent = $(“#titlebox”).val();

11

// get the body of the post

12

var bodycontent = $(“#contentbox”).val();

13

// get the tags

14

var tagscontent = $(“#tagsbox”).val();

The postIt function begins by using jQuery to fetch the values entered into the three text areas. (Just in case you don’t know jQuery, “$(“#titlebox”) gets the element with the ID of “titlebox.” And if you don’t want to use jQuery, you can get the same result with: var titlecontent = document.getElementById(‘titlebox’).value.

16

// get an array of all checkboxes in the div

17

var checks = $(“#categories”).find(“input”);

18

// create an empty array

19

var cats = new Array();

20

// go through all the checkboxes

21

for (var i=0; i < checks.length; i++){

22

// is this one checked?

23

if ($(checks[i]).is(‘:checked’)){

24

// if so, then push its value into the array

25

cats.push( $(checks[i]).val() );

26

}

27

}

Creating an array of categories takes a little more work. Line #17 creates an array (“checks”) of all of the checkboxes in the div with the id “categories.” Lines #21-27 look at each of the checkboxes in that array. If line #23 sees that a particular checkbox has in fact been checked, then it puts the value of that checkbox into the created on line #19. (You want the value to be exactly the same as the name of the category in your WordPress installation. Also, remember that the checkbox’s value is not necessarily the same as the text displayed to the user.)

29

$.ajax({

30

type: “POST”,

31

url: “postViaXmlrcp.php”,

32

dataType: JSON,

33

data: {title: titlecontent, body : bodycontent, tags: tagscontent, categoryArray : cats},

34

error: function(e){

35

if (e.responseText.indexOf(“Successfully”) > -1){

36

alert(“Success! Post has been posted! Let the regrets begin!”);

37

}

38

else{

39

alert(‘Error posting blog via xmlrpc: ‘ + e.responseText);

40

}

41

}

42

})

Now we call the PHP script that we created above. We do this via AJAX, using the jQuery syntax, which is much simpler than the native JavaScript way of doing it. Lines #30-40  specify the communication the JavaScript will have with the PHP file.

Line #30: The “POST” here has nothing to do with posting a blog post. It’s stating what sort of transaction we’re about to have with the PHP script.

Line #31: This is the path to the PHP file we’re going to run. If it’s in the same directory as this HTML file, you don’t have to monkey with a path name.

Line #32: We’re going to pass data to the PHP script in the JSON way of expressing data.

Line #33: This creates the JSON we’re going to send. It’s all within curly brackets. There are four phrases, separated by commas. Each phrase consists of a keyword (which you can think of as being like a variable) and a value. We are free to make up whatever keywords we want, so long as those are the keywords we use in the PHP file to fetch the data that they label; see lines #7-10 in the PHP script above.

Line #44: If there is an error in the PHP, it will send back some information. There is also an equivalent “success:” function available. But I’m doing something wrong, because even when the PHP works and the blog gets posted, I still get an error message. If you go back to Line #33 of the PHP, you’ll see that if the PHP succeeds, it sends the message “Article Posted Successfully.” For reasons I don’t understand, that message shows up in the “error:” function of the AJAX. So, I check the message. If it has the word “Successfully” in it, the script alerts the user that the post has been posted. If it does not, on line #39 it posts an error message.

That’s it. If it doesn’t work, it’s because you’re doing something wrong,  starting with listening to me. Obviously I can’t help you since I don’t even know how this thing works.

Good luck!

3 Comments »

July 5, 2014

Convert Javascript to an HTML table

I was writing a long-ish tutorial about how to use LibraryCloud that refers to bunches of Javascript. I wanted a way to refer to that code in the HTML document. So I wrote a converter that turns Javascript code (and maybe others) into HTML tables you can just plunk into your document.

Here’s an example of some code:

function init(){
// get textarea to accept tab key
// thank you http://stackoverflow.com/questions/6140632/how-to-handle-tab-in-textarea
	$("#rawjs").keydown(function(e) {
		if(e.keyCode === 9) { // tab was pressed
			// get caret position/selection
			var start = this.selectionStart;
			var end = this.selectionEnd;

			var $this = $(this);
			var value = $this.val();

			// set textarea value to: text before caret + tab + text after caret
			$this.val(value.substring(0, start)
						+ "\t"
						+ value.substring(end));

			// put caret at right position again (add one for the tab)
			this.selectionStart = this.selectionEnd = start + 1;

			// prevent the focus lose
			e.preventDefault();
		}
	});
}

And here’s what it looks like once converted into an HTML table:

1

function init(){

2

// get textarea to accept tab key

3

// thank you http://stackoverflow.com/questions/6140632/how-to-handle-tab-in-textarea

4

$(“#rawjs”).keydown(function(e) {

5

if(e.keyCode === 9) { // tab was pressed

6

// get caret position/selection

7

var start = this.selectionStart;

8

var end = this.selectionEnd;

 

9

var $this = $(this);

10

var value = $this.val();

 

11

// set textarea value to: text before caret + tab + text after caret

12

$this.val(value.substring(0, start)

13

+ “\t”

14

+ value.substring(end));

 

15

// put caret at right position again (add one for the tab)

16

this.selectionStart = this.selectionEnd = start + 1;

 

17

// prevent the focus lose

18

e.preventDefault();

19

}

20

});

21

}

There’s definitely some clunkiness to this. For example, if you click on the “toggle line numbers” button it hides or shows the line numbers for every such table in your document. (I’ll fix this eventually.) By the way, the “Toggle” button is there so your readers can select the code and not get the lline numbers.

Also, in order to get the formatting totally encapsulated within your document, I clunkily add a “<style>” section before each table. I couldn’t figure out any other way to do it without making your HTML document dependent on my server being up.

The utility is here: http://hyperorg.com/programs/convertJStoTable/convertJStoTable.html. The code itself is up at Github (but not quite up to date at the moment).

Please let me know all the ways it’s broken and sucks, although I don’t promise to do anything about it. It worked for me.

1 Comment »

September 9, 2012

Beginner2Beginner: Javascript multi-file upload + PHP to process it

Much as I love being a hobbyist programmer, it can sometimes be frustrating as all get-out. Sometimes it’s just a bug because I made a dumb error (getting a variable’s scope wrong) or because I made an assumption about how something works (BBedit‘s hex dump does not show you the content of the file on the disk, but of the file in memory, including the line endings it’s transformed). But then there are the frustrations that come from not having the slightest idea of the basics. The worst are the basics that are so basic that the explanations of them assume you already know more than you do.

Welcome to the world of pain known as uploading multiple files using Javascript. For example, suppose you are writing an app that lets users take notes on an article using a plain old text processor. They can then upload those note files to some code that processes them, perhaps turning them into a database. Rather than having users upload one file at a time, you want to let them upload a bunch.

There are plenty of slick, free examples on the Web that provide beautiful front ends for doing this. Many of them I could get to work, but not exactly the way that I wanted. Besides, I was really really really confused about what happened after the front end.

So, after a lot of skullpounding and forehead slapping, here’s a guide. But please take seriously this warning: I barely know what I’m doing, and I’m undoubtedly doing this in the clunkiest fashion possible. I am very likely getting important things wrong. Some of them may be fatal, at least in the programmatic sense. (If you have a correction, please let me know. Note I may fix the code that follows, rather than doing the usual — and proper — Web thing of striking through the errors, etc.) Here goes….

To enable uploads, you’ll be writing code that will live in two places. The user interface code is in the HTML running in the user’s browser. The files are going to be uploaded — copied — to a web server. The code in the browser is going to be Javascript. The code on the server is going to be PHP, because Javascript is for browsers. (Oversimplification noted.) Those two places can be physically the same machine. If you’re using a Mac, a web server comes standard; if you don’t have a web server handy, there are bunches of free ones; that’s beyond the scope of this post.

After experimenting with many of the beautiful packages that are available, I gave up and went with the HTML5 solution. Modern browsers will have no problem with this. If you need to design for the world of old-fashioned browsers, then you need to find a competent explanation. In short: I love you, but go away! (Apparently, if you use the HTML5 solution, it will still be usable in lesser browsers, although it will allow users to select only one file at a time.)

HTML5 makes it ridiculously easy to do the user interface bit: you just tell it you want to allow multiple selections within a file-chooser dialogue. This comes from a post by Tiffany Brown:

<form action="processThem.php" method="post" enctype="multipart/form-data">
<input type="file" value="" name="upload[]" multiple>
<button type="submit">Upload!</button>
</form>

This will create an input field that when clicked will launch a file-browsing dialogue box in which the user can select multiple files. It then sends it to the PHP script called “processThem.php” on your server. The two key points to note are that the first line does the work of allowing multiple choices, and the “[]” in the name turns that variable into an array that will pass the entire list of user choices to the PHP script waiting on your server.

Here’s what it looks like, although this particular example won’t actually upload anything:



Now you have to create the “processThem.php” script (or whatever name you’ve specified) on your server. The uploaded files get placed in an array called $_FILES. But, they don’t get stored on the server for long: they are stored in a temporary folder from which they are automatically deleted after a little while. So, you need to process them, and quite possibly move them for permanent storage to a folder of your choosing. Here’s some sample PHP code from an anonymous commenter (“Me”) on the Tiffany Brown post:


<?php
$error_message[0] = "Unknown problem with upload.";
$error_message[1] = "Uploaded file too large (load_max_filesize).";
$error_message[2] = "Uploaded file too large (MAX_FILE_SIZE).";
$error_message[3] = "File was only partially uploaded.";
$error_message[4] = "Choose a file to upload.";

$upload_dir  = './tmp/';
$num_files = count($_FILES['upload']['name']);

for ($i=0; $i < $num_files; $i++) {
    $upload_file = $upload_dir . urlencode(basename($_FILES['upload']['name'][$i]));

    if (!preg_match("/(gif|jpg|jpeg|png)$/",$_FILES['upload']['name'][$i])) {
        print "I asked for an image...";
    } else {
        if (@is_uploaded_file($_FILES['upload']['tmp_name'][$i])) {
            if (@move_uploaded_file($_FILES['upload']['tmp_name'][$i], 
                $upload_file)) {
                /* Great success... */
                echo "hooray";
                //$content = file_get_contents($upload_file);
                //print $content;
            } else {
                print $error_message[$_FILES['upload']['error'][$i]];
            }
        } else {
            print $error_message[$_FILES['upload']['error'][$i]];
        }    
    }
}
?>

Let’s walk through this.


$error_message[0] = "Unknown problem with upload.";
$error_message[1] = "Uploaded file too large (load_max_filesize).";
$error_message[2] = "Uploaded file too large (MAX_FILE_SIZE).";
$error_message[3] = "File was only partially uploaded.";
$error_message[4] = "Choose a file to upload.";

In the first few lines, Me does us the favor of providing non-technical explanations of possible errors, so that if something goes wrong, it will be easier to know exactly what it was.


$upload_dir  = './tmp/';

Then Me designates a particular folder as the container for the uploaded files. Me chooses one called “tmp” in the same directory as the PHP script. (Make sure you have such a folder and the permissions are set, or, of course, create one with whatever name you’d like.)


$num_files = count($_FILES['upload']['name']);

Then Me gets a count of how many files were uploaded, and stores it in the variable $num_files. You tell that variable that you want the number of files that were included in “upload[]” in the form on your HTML. (You can use whatever name you want on that form, so long as you use the same one in your PHP.)


for ($i=0; $i < $num_files; $i++) {
    $upload_file = $upload_dir . urlencode(basename($_FILES['upload']['name'][$i]));

Then Me loops through all the files, assigning each one in turn to the variable $upload_file. But notice the weirdness of this part of the line:

$upload_file = $upload_dir . urlencode(basename($_FILES[‘upload’][‘name’][$i]));

First the easy parts. The baseline function returns just a file’s name without any path information; we want that because the point of this line is build a path to where the file will be saved in the folder you’ve set up for it. Also, I added the urlencode function in case the name of the file your user uploaded contains spaces or other characters that makes your server barf.

Now consider $_FILES[‘upload’][‘name’][$i]. It’s got those weird bracketed terms because $_FILES is an associative array. You can think of the usual sort of arrays as pairing a value with a number; give the array the number and it returns the value. In an associative array, values are paired with arbitrary keys (i.e., a word); give it the key and it returns the value. Here are the pre-defined keys for the associative array that gets sent to the PHP script when a user uploads files:

  • name: The file name of the uploaded file
  • type: Is it an image? A music file? etc.
  • size: The size in bytes
  • tmp_name: The crazy-ass name of the copy being stored on the server
  • error: Any error codes resulting from the upload

So, suppose you’re cycling through the array of uploaded files as in our example, and you want to get the name of the current file (i.e., file $i in the sequence):


$fname = $_FILES['upload']['name'][$i];

The [‘upload’] designates the array of all uploaded files. The [$i] pulls out of that array all of the information about one particular uploaded file, just like with ordinary array. The [‘name’] gets the value associated with that key for that particular file. As $i is incremented, you get the name of each file. If you wanted the crazy-ass temporary name, you would put in tmp_name instead of name, and likewise for the other parameters.


if (!preg_match("/(gif|jpg|jpeg|png)$/",$_FILES['upload']['name'][$i])) {
        print "I asked for an image...";
    }

Next Me optionally checks the name of the uploaded file for a particular set of extensions in case you want to limit the uploads to images (as in Me’s example) or whatever. Me is using regex to do the work, a function for which I am doomed to a copy-and-paste understanding.


  if (@is_uploaded_file($_FILES['upload']['tmp_name'][$i])) {

Next Me does a check to make sure that the file was actually uploaded and is not the result of malicious code. is_uploaded_file is a built-in PHP function. The preceding “@” suppresses error messages; I don’t know why Me did that, but I’m confident it was the right thing to do. While you are debugging, you might want to take the @ out.


move_uploaded_file($_FILES['upload']['tmp_name'][$i], $upload_file)

If the file passes that test, then Me moves it to the folder s/he designated. Note that we’ve already put the pathname to the storage folder into $upload_file.

Obviously, where the script has the comment “Great Success” you would put the code that does what you want to do to the uploaded file. I have added two lines — commented out — that get and print the content of the file; that gets ugly if the file isn’t some type of text file.

So, please let me know what I’ve gotten wrong here. (And maybe sometime I’ll give you some truly embarrassing code for returning the results of the PHP on the page that has the upload form on it.)

7 Comments »

July 22, 2012

Capturing control keys in Chrome et al.

Hallelujah! For years — literally years — I’ve been limping along with a blender full of spaghetti to do something that should be really simple: capture control key combos (like CTRL-S or CTRL-I) via Javascript in all the major browsers. I finally found some simple code that seems to work beautifully.

The problem is that the browsers don’t agree about what’s going on when a user presses a control key and another key simultaneously, which is, after all, the usual thing people do with the control key. Some of the browsers think that it’s two events, so you have to record the control keypress, remember it, and treat the next keypress differently. Other browsers think of it as a single keypress that you can just process as a if CTRL-S were a unique key. Then, depending, you may or may not have to nullify the S press. The way I was doing it (cribbed from multiple sources, of course) involved first checking on which browser the Javascript was running in, and then process keystrokes, looking for an initial control press. Pain in the butt, and it was fragile.

I am certain that this is not a problem for actual developers. For example, jquery handles keystrokes, although I had trouble getting it to work (becaues, if it’s not clear, I am a ham-fisted hobbyist who mainly just copies in other people’s code. Thank you, other people!)

Today I had a a few minutes, so I went back to Google and found some simple code from Ganesh. Thank you!

Here’s what you do:

First, include jQuery. Place the following into your Javascript the following block. Put it toward the top, and don’t put inside a function. You want it to run whenever your Javascript loads. (Well, you could put it into an initialization function if you want.)

$.ctrl = function(key, callback, args) {
    $(document).keydown(function(e) {
        if(!args) args=[]; // IE barks when args is null 
        if(e.keyCode == key.charCodeAt(0) && e.ctrlKey) {
            callback.apply(this, args);
            return false;
        }
    });        
};

After that, you can bind a control combo, such as Control-S, to the function you want (e.g., “SaveMe()”) this way:

$.ctrl('S', function() {
    SaveMe();
});

By the way, Ganesh thanks an anonymous commenter for improving the code. God bless the iterative Web!

1 Comment »

August 1, 2010

Beginner to Beginner: When a Javascript program only works in a debugger

[LATER the same day:] Thanks to expert advice from some generous expert folks in the comments, I found the mistake I was making; I explain it in the comment #5. But, the mystery of why it worked only when run in debug mode remains. (Tom in comment #4 has a good hypothesis.) Nevertheless, I’m sticking with my overall point that for a home-made, one-user project like this, a brute-force, dumb-ass solution sometimes serves. I should maybe mention also that one reason I post about “programming” is precisely because I’m an idiot about it, and I think it’s good to be a beginner in public.

Aaarrrggghhh. I spent way too much time yesterday and today trying to fix a problem with a javascript program the right way. When I finally gave up and did it the wrong way, it worked fine…except for being embarrassingly inelegant.

It’s a mystery. The program doesn’t work ever except if you run it in a debugger and set a breakpoint anywhere in either of the two affected functions. It doesn’t even work if you put an “alert()” into it. It really really wants a breakpoint. This is consistent in both Firefox and Chrome, with Firebug and the integrated Chrome debugger. It only works if you set a breakpoint. It does not work if you do not set a breakpoint. Did I mention Aaaarrrrrrggggghhhhhh?

Some details: I added a function to my little all-crap blog editor. The function (call it FunctionA) gets called when the user (= me and only me) presses a button. FunctionA calls FunctionB to see if the selected text is listed in an array of terms. If so, then FunctionA inserts some text using associated text. (To be more concrete, although it doesn’t matter: If the selected text is the name of someone for whom I have a twitter name, FunctionA builds and inserts a link to the person’s Twitter address.) FunctionB calls one other function (that trims spaces and other cruft) and one method (to temporarily uppercase both the selected text and the name in the database, so that the search won’t be case sensitive, and yes, I’m sure there’s a better way to do that). Then it simply walks through the array, looking for a match.

I googled around and didn’t find much help with the mysterious fact that it only works if I set a breakpoint. People with similar problems can sometimes solve them by using the setTimeout() function, to let the script catch up with itself. But, that’s not really what setTimeout does, apparently. It doesn’t pause the script. Rather, it delays the execution of a particular function while the rest of the script continues merrily on. I also found that a common cause of a similar problem is that your script is trying to access a part of the document that hasn’t been loaded or created yet. But: (a) My script isn’t trying to do that, and (b) If that were the problem, then putting in an alert() would have worked for me, no? The cause might be some multithreaded mystery for which there is no known solution, but I really don’t know. (You might try this.)

So, after looking at some clever recursive setTimeout strategies that I couldn’t figure out how to adapt to my particular script, I had a stupid idea. I put the code from FunctionB — the one that looks the selected text up in an array — straight into FunctionA. So, FunctionA doesn’t call FunctionB. It has within it a copy of the code in FunctionB.

And it works.

It works even though it is just about the definition of “inelegant.” The whole point of every programming language since Turing first ran marked paper off a mental spool is to write functions for any process your script might use more than once. Not only does that save time writing, it makes debugging a billion times easier because you only have to fix it in one place. So my solution to my problem is like telling a chef to beat each egg separately or to suggest reloading a supersoaker after each and every squeeze.

But, dammit, it worked. I don’t know why, and I ‘d say that I don’t care, but, I do. Well, at least I can now get back to wasting my time in marginally less frustrating ways.

9 Comments »

July 9, 2010

Outlining in your browser

Dave Winer just linked to a bookmarklet — ijotEdit by Marc Barrot — that lets you write outlines in your browser. It’s OPML-based, of course. Very nice.

I have to admit, though, that I played with it with mixed emotions. I have nothing negative to say about it at all. On the contrary. And that’s the problem. For the past two months, my hobby has been writing an OPML-based, browser-based outliner because I couldn’t find free one that worked across Mac, Linux and Windows. I just two days ago got mine to the point of minimum usability, although it’s still full of bugs. But you can save and load the outlines you create with it, and it exports in RTF, HTML and text pretty reliably. The functionality is truly minimal: You can use the arrow keys to move lines or lines + children, but you can’t cut and paste structures; there are no tools for including links or graphics; it doesn’t do rich text; if you want to change the styles, you have to hand-edit the css file. But, hey, the little right and down arrows work! I could never get it to the point that I would share it with anyone else, and if I ever showed the code to an actual developer, the milk would come out her nose. But I got the arrow keys working in IE as well as Firefox and Chrome! (Well, the place I copied the key capture code from said that it would work in IE.)

So, why mixed feelings about Marc’s actual, real, working outliner? Simple: I lost the excuse for writing my own. I’m a hobbyist, so it shouldn’t matter. Oddly, it does.

8 Comments »

July 2, 2010

Beginner-to-beginner: Parsing OPML, including level (= indents)

I am 99.9% confident that I’ve spent many hours trying to do in a complex way something that javascript (perhaps with jquery) lets us do with a single line of compressed code. But, I couldn’t find that line. Besides, I’m a hobbyist which means the puzzle is usually more fun than the solution. But this one drove me crazy.

I’m trying to read an OPML file. OPML is an instance of XML designed to record outlines. I found plenty of scripts online (javascript and php) that would read the contents, but none that would tell me the indentation level of each line. I’m sure there’s some simple way of figuring that out, but it beat the heck out of me. So, I finally found some javascript by Vivin Pallath to which I could add a couple of lines (mainly by trial and error) to record indentation info.

Vivin’s script is, appropriately recursive. That’s because outlines are arbitrarily nested, so you have to be able to call a “get the children” function on each line, each of which may have its own children. A recursive function calls itself, in this case until it reaches the end of a branch, at which point it pops up to the next sibling branch and carries on. You can easily record each branch in a flat list, but I also need to know how far down the limb we are. I find recursion to be extraordinarily difficult for my brain to follow, so more or less by trial and error I added a counter (a global variable, although I’m not sure that’s required), and then tried to figure out when it needs to be reset to zero.

The way I have it set up, the tree walking function looks for XML elements called “outline,” because that’s the tag OPML uses. It records the text content of the line in the outline as the attribute “text.” (OPML also can encode stuff other than text, but I happen not to care about that.) At each line, it records the text and the level number in a global array. This is a rather silly way of recording the info, but that’s the easy part to fix.

The parseOpml function consists mainly of code from Phpied that opens an xml file and reads it into an xml document object that javascript knows how to work with. The parseOPML function calls the walkTree function that does the actual parsing. The walkTree function takes as parameters the element from which it should start walking (which, in this case, is the root of the xml object itself) and the starting level of indentation (0). It wouldn’t be hard to add to it the name of the element you want it to pay attention to (which, in this case, is specified in walkTree to be “outline”>

Now for the caveats: I’ve barely tested this. It may break under predictable circumstances. It is very likely wildly inefficient (which doesn’t matter for my petty uses). It will undoubtedly embarrass actual developers. But, maybe it will help some other hobbyist…

// globals
var treeArray = new Array(); // array to hold results
var glevel; // the indent level

function parseOpml(){
  // opens the xml file and calls the tree walker
  

var fname="/pathname/to/your/file.xml";

// open the xml file (from http://www.phpied.com/opml-to-html-via-javascript/) var xmlDoc = null; if (window.ActiveXObject) {// code for IE xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); } else // code for Mozilla, Firefox, Opera, etc. if (document.implementation.createDocument) { xmlDoc = document.implementation.createDocument("", "", null); } else { alert('Your browser cannot handle this script'); } if (xmlDoc != null) { xmlDoc.async = false; xmlDoc.load(fname); } // call the tree walker treeArray.length=0; // reset global array walkTree(xmlDoc,0);

} //--------------- Recursive tree walker --------------

function walkTree(node, glevel){ // walks tree looking for elements tagged "outline" // Replace the two instances of "outline" below to have it // look for other tags //Original function from: Vivin Pallath. Thanks! //http://vivin.net/2005/07/01/innerhtml-alternative-for-xhtml-documents-in-firefox/ //Modified to count indentation levels var n="d"; var a="";

if (node.hasChildNodes()) { node = node.firstChild; do{ // only return elements tagged "outline n = node.nodeName; if (n == "outline") { glevel++; // increase level // pushes the text and level as a string into a global array a = node.getAttribute("text"); // get the text treeArray.push(glevel + "=" + a); } walkTree(node,glevel); // recurse until no children node = node.nextSibling; // pop up to get next sibling // If this branch has no more children, reduce the level if ((n=="outline") && (!node.hasChildNodes())) { glevel = glevel - 1; } } while(node); } }

1 Comment »

January 16, 2010

Convert text with URLs to text with hyperlinks

Using a Javascript regex function written by Sam Hesler at StackOverflow (thanks!), I’ve posted a simple little page that lets you turn text that has URLs in it into text that has clickable, hyperlinked URLs. That is, you go from http://www.globalvoicesonline.org to
http://www.globalvoicesonline.org.

The page is: ConvertURLsToLinks.html. It’s quite bareboned, and I’m sure there are lots of sites that do a far better job with many more options. But, it worked well enough for the one job I wanted it for, so maybe it’ll work for you. At least it won’t destroy your original text (although keep the original just in case.)

The regex function I borrowed from Sam Hesler is:

function replaceURLWithHTMLLinks(text) {
var exp = /(b(https?|ftp|file)://[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/ig;
return text.replace(exp,”$1“);
}

7 Comments »

November 29, 2008

Beginner to Beginner: Splitting strings into arrays in Javascript

I mentioned to my nephew Joel Weinberger, a CS grad student at UC Berkeley, that I wished the Javascript “split” method took multiple delimiters, and within minutes, he wrote one for me. If you know what I’m talking about, you can click here to get a zip file with the code (including a function as well as a method) and a sample. If you don’t …

[Note: all explanations are approximate.] Javascript comes with a built-in method for converting a string (that is, what normally consists of letters and characters in quotes) into an array (that is, a data structure of numbered elements). So, if you have a string that’s really a list of elements, such as “monday, tuesday, wednesday” or “12-345-6,” the split method will automatically chop it up into an array, using a delimiter of your choice (a comma or a dash in the two examples given). This is very useful.

But suppose you have a string such as this: “beef OR chicken AND duck” You want to be able to chop it up at the ORs and the ANDs, but the split method only lets you specify one delimiter.

Enter Joel. His multiSplit method lets you specify an array of delimiters. It chops up the string and records the phrases and their delimiters. Very handy.

Thanks, Joel!

[Tags: ]

4 Comments »

July 29, 2008

Reason #12,563 I love the Web

I’m a-lovin’ Marijn Haverbeke’s Eloquent Javascript, an interactive javascript tutorial. It’s clear, nicely written, nice looking, handy (what with its embedded console for trying scripts out), free, and Creative Commons licensed. It’s easily downloadable so you can run/read it even when you don’t have any of that newfangled “broadband” the kids are so excited about.

Thank you, Marijn. [Tags: ]

Be the first to comment »