So in the last week or so I've been working on a bunch of CMS related tasks. I've added some new stuff and, for the first time, 3rd party scripts are now in our repository. Why? Well, why re-invent the wheel, right? In some cases I've implemented a few changes in these libraries, while in others I've rewritten them a LOT. You'll find working examples and details in the wikitorials and I'll be updating our docs shortly.

simple.editor.js

As much as I'd like to implement a wysiwyg editor or use one of the 3rd party ones out there like TinyMCE or FCKeditor, or even the Mootools wysiwyg by Inviz, it doesn't suit my needs right now. This annoys me, but the fact is that wysiwyg editors built in browsers are fraught with peril. Creating one with flash is an option, but I'm not a flash expert.

So what I've created is an html editor that just helps you wrap your content with custom tags. I need to add tag support and that sort of thing like posteditor (or I might just integrate posteditor).

Anyway, basically this works kinda the way my form validator works. You create commands (like "bold" or "underline") and add them to the editor's list of commands. You can add commands to an instance or to the global set. Then you add buttons to your editor that reference the command and you're set.

Here's an example of what it takes to add a command to the editor:

JavaScript:
/*  Default commands: */
SimpleEditor.addCommands({
    /*  bold - <b></b>    */
    bold: {
        shortcut: 'b',
        command: function(input){
            input.insertAroundCursor({before:'<b>',after:'</b>'});
        }
    },
    /*  underline - <u></u>   */
    underline: {
        shortcut: 'u',
        command: function(input){
            input.insertAroundCursor({before:'<u>',after:'</u>'});
        }
    }
});

drag to resize


And then to make these work, you add a link or image or whatever to your editor like so:

HTML:
<img src="bold.gif" width="20" height="20" alt="Bold (ctrl+b)" title="Bold (ctrl+b)" rel="bold"/>
<img src="underline.gif" width="20" height="20" alt="Underline (ctrl+u)" title="Underline (ctrl+u)" rel="underline"/>

drag to resize


In this case I use images and pass those along when I create a new instance of the editor. It uses the rel attribute to map to the command.

See a working example in the wikitorial.

Autocompleter

I needed an Autocompleter and once again digitarald has beat me to it. I liked this script a whole hell of a lot but it was missing one big thing: multiple values. Now, the right way to implement multivalues with autocomplete is that the user starts typing, valid options show up in the drop down, they click one, and it ads this value to a different dom element, presenting the user with a now empty input. This is how tagging works on flickr and just about every where else. But for our common library I wanted to implement a simple solution for this for others that might want to use it, so I rewrote most of the whole thing. I sent these changes back to digitarald, and he's going to incorporate them into his release (probably).

Autocompleter.JsonP

I also integrated our JsonP class into the script so that we could use this against the API. Here's a working example; type in "ipod".

Execute the block below and then type something into the input above.

JavaScript:
new Autocompleter.JsonP($('jsonp'), 'http://api.cnet.com/restApi/v1.0/techProductSearch',
{
  jsonpOptions: {
    //this data gets added to the query string using JsonP's options
    data: {
      viewType: 'json',
      partKey: '19926949750937665684988687810562', //this is my code, user your own!
      iod:'none',
      start:0,
      results:10
    }
  },
    inheritWidth: false,
    dropDownWidth: 600,   
  //require at least a key stroke from the user
  minLength: 1,
  //this function filters the results based on the input
  filterResponse: function(resp) {
    //test it
    if(!choices || choices.length == 0) return [];
    //filter it and return it
    var regex = new RegExp('^' + (this.queryValue || '').escapeRegExp(), 'i');
    return choices.filter(function(choice){
      return (regex.test(choice.Name.$) || regex.test(choice['@id']));
    });
  },
  useSelection: false,
  //because the data returned has a unique structure, we must manage the parsing ourselves
  filterResponse: function(resp) {
    try {
      //this structure is unique to the CNET API
      choices = resp.CNETResponse.TechProducts.TechProduct;
      //test it
      if(!choices || choices.length == 0) return [];
      //filter it and return it
      return choices.filter(function(choice){
        return (choice.Name.$.test(this.getQueryValue(), 'i') || choice['@id'].test(this.getQueryValue()), 'i');
      }.bind(this));
    } catch(e){'filterResponse error: ', dbug.log(e)}
  },
  injectChoice: function(choice) {
    //again, the structure of these items is unique to the CNET API
    if(! choice.Name.$)return;
    var el = new Element('li')
      .setHTML(this.markQueryValue(choice.Name.$))
      .adopt(new Element('span', {'class': 'example-info'}).setHTML(this.markQueryValue(choice['@id'])));
    el.inputValue = choice.Name.$+' ('+choice['@id']+')';
    this.addChoiceEvents(el).injectInside(this.choices);
  }
});
new Autocompleter.JsonP($('jsonp'), 'http://api.cnet.com/restApi/v1.0/techProductSearch',
{
jsonpOptions: {
//this data gets added to the query string using JsonP's options
data: {
viewType: 'json',
partKey: '19926949750937665684988687810562', //this is my code, user your own!
iod:'none',
start:0,
results:10
}
},
inheritWidth: false,
dropDownWidth: 600,
//require at least a key stroke from the user
minLength: 1,
//this function filters the results based on the input
filterResponse: function(resp) {
//test it
if(!choices || choices.length == 0) return [];
//filter it and return it
var regex = new RegExp('^' + (this.queryValue || '').escapeRegExp(), 'i');
return choices.filter(function(choice){
return (regex.test(choice.Name.$) || regex.test(choice['@id']));
});
},
useSelection: false,
//because the data returned has a unique structure, we must manage the parsing ourselves
filterResponse: function(resp) {
try {
//this structure is unique to the CNET API
choices = resp.CNETResponse.TechProducts.TechProduct;
//test it
if(!choices || choices.length == 0) return [];
//filter it and return it
return choices.filter(function(choice){
return (choice.Name.$.test(this.getQueryValue(), 'i') || choice['@id'].test(this.getQueryValue()), 'i');
}.bind(this));
} catch(e){'filterResponse error: ', dbug.log(e)}
},
injectChoice: function(choice) {
//again, the structure of these items is unique to the CNET API
if(! choice.Name.$)return;
var el = new Element('li')
.setHTML(this.markQueryValue(choice.Name.$))
.adopt(new Element('span', {'class': 'example-info'}).setHTML(this.markQueryValue(choice['@id'])));
el.inputValue = choice.Name.$+' ('+choice['@id']+')';
this.addChoiceEvents(el).injectInside(this.choices);
}
});

drag to resize


More in the wikitorial.

Expect more in the next week or so.