MooTools Drag.Ghost →
If you’ve used YAHOO.util.DDProxy, this is a MooTools version. Basically, drag around a “ghosted” version of the element, so you can leave the original behind in case of a bad drop.
new Drag.Ghost(element [, options]);
If you’ve used YAHOO.util.DDProxy, this is a MooTools version. Basically, drag around a “ghosted” version of the element, so you can leave the original behind in case of a bad drop.
new Drag.Ghost(element [, options]);
Internet Explorer doesn’t support HTML5 elements. You must provide a shim. But this only helps fix any markup that is in the original document. If you receive more markup via an ajax request, and want to insert it into the document, IE has a hissy fit. You can’t stick HTML like that into some elements innerHTML property and hope it parses correctly.
What I did instead, was before inserting the HTML, I’d find all HTML5 elements1, convert them to spans with an extra attribute htmlfix with the value set to the tag name it should be. Then after inserting into an innerHTML, I’d find all the spans with an htmlfix attribute, create the proper element, transfer all its attributes, set its innerHTML, and replaceNode.2
function processAjax(content) {
var spanned = content,
html5els = ['nav','header','article','footer','section','time'], //etc...
attributes = ['id', 'class', 'pubdate', 'datetime']; //etc...
html5els.forEach(function(tag) {
var re = new RegExp('<(\/?)'+tag+'([^>]*)>', 'g');
spanned = spanned.replace(re, '<$1span htmlfix="'+tag+'"$2>');
});
var el = new Element('div', { html: spanned }).getFirst();
//after IE parses the spans, createElements of all the "fixed" elements
el.getElements('span[htmlfix]').forEach(function(span) {
var tagname = span.get('htmlfix');
span.erase('htmlfix');
var newEl = new Element(tagname, { html: span.innerHTML });
//transfer attributes
attributes.forEach(function(attr) {
var val = span.get(attr);
if(val) {
newEl.set(attr, val);
}
});
newEl.replaces(span);
});
return el;
}
Yes, I used Regex to parse HTML. No, it didn’t create a rift in space. I knew what I was doing. ↩
This uses MooTools, because it’s awesome. I wasn’t feeling very masochistic this time around, so I didn’t create a vanilla JS version. ↩
More than you might ever want to know about JavaScript prototypes.
Regarding web apps:
The groundwork has been laid by some of the best thinkers in our industry, and neglecting to build a proper stack, to me, pushes all of that hard work aside in favor of (too) rapid deployment. If you’re metrics show a 99% JavaScript enabled audience, are you willing to forsake that 1%?
Certainly, it’s unacceptable for content based sites (brochure sites, blogs, etc) to require JavaScript. But he’s talking about more powerful, interactive web applications, like MobileMe and GMail. I’d agree that if the content can be reasonbly accessible without JavaScript, it should be. I’d say its borderline with GMail, but they do it. Props to them.
Like he says though, some applications exist for exactly what you can do with JavaScript enabled. Such as Blazonco, where what we’re offering is the ability to drag and drop your content. That’s one of the reasons to use it. So should we offer the ability to edit your page without it? I don’t see a user-friendly solution of converting draggable components into a mobile-friendly text block.
I’m starting to really like the idea of writing less CSS. Especially with all the browser-prefixed properties. It’d be nice to just write one border-radius and have a processor expand it for me.
Less looks promising. Though I’m hesitant about having the processor execute on the client side. It does cache itself in localStorage if the browser supports it. But what about ones that don’t (all of IE)? IE doesn’t have a blazing fast JavaScript interpreter to begin with.
Plus: Every single visitor must spend CPU cycles processing the LessCSS. When the exact same output will be reached every time, I would rather just do it once server side and then serve up a static CSS file. Processed once, cached by all browsers.
I originally setup my Tumblr unsure of what to use it for, so I just added a bunch of feeds to it, like my Twitter, my blog feed, my delicious links, and my github activity. But when I decided to actually use my Tumblr as a real blog, I decided I didn’t want all that junk in here.
For whatever reason, Tumblr doesn’t provide any sort of simple way to clean out a lot (1000+) of posts. The only way to delete posts it to scroll to the post in the dashboard, click the “delete” button, and wait for the POST to be submitted and the page reloaded, which forgets your current scroll position. I wasn’t going to do that 1000 times.
Thankfully, there’s a decent API that lets you easily receive all your posts. The posts have a field set if they had been imported through a feed. And with the dashboard using Prototype, it was a matter of putting together 2 functions to read and delete posts, and then some minor babysitting to make sure to pause when Tumblr would throttle me.
var keepGoing = true,
tumblr = '',
email = '',
password = '',
posts = 0;
function findFeedItems(json) {
json.posts.each(function(p) {
if(p['feed-item']) {
apiDelete(p['id'])
}
});
if (keepGoing) {
apiRead.delay(20);
}
}
function apiRead() {
console.log('reading...');
var nonce = new Date();
var s = document.createElement('script');
s.src = "http://"+tumblr+".tumblr.com/api/read/json?num=50&callback=findFeedItems&start=" + posts + "&nonce=" + (+nonce);
document.body.appendChild(s);
}
function apiDelete(id) {
console.log('deleting: %s', id);
new Ajax.Request('/api/delete', {
method: 'post',
parameters: 'email='+email+'&password='+password+'&post-id=' + id,
onSuccess: function() {
console.log("Successful deletion: %s", id);
},
onFailure: function(transport) {
console.log('delete failed: ', transport.status);
}
});
}
While on the dashboard, copy and paste this into your console, with the proper variables filled in, of course.
Just called apiRead() to get it started. When you see a delete failure, you can pause your purge by setting keepGoing to false. Wait a few seconds, set it back to true, and call apiRead() again.
Once you’re no longer seeing successes or failures, but you know theres more posts, you can set the posts variable to 50, or 100, or higher. That will start the search farther down your posts.
Again, this script will delete everything imported from a feed. If you want to just delete everything, pull out the if(p['feed-item']) condition, and just pass all the posts to apiDelete.
I wrote about the setter/getter API of Elements, and when to use store/retrieve instead.
The past couple weeks I’ve been working on a website that, with JavaScript enabled, will never refresh the page1. With MooTools, I just needed to manipulate the URL hash and use Request, making it fairly easy to get an Ajax site that works like Facebook. Granted, this has nothing about handling CSS or JavaScript assets for each page. I’m currently handling that myself, and perhaps I’ll share my findings for that another time.
First of all, I started with making every link in the site need to trigger the Ajax loading. MooTools More has an excellent Event Delegation package. Using that, I’ll list for every click on a link on the page, check if it’s to the same domain, and load those pages asynchronously, otherwise just let the browser travel to the external page.
$(document.body).addEvent('click:relay(a)', function(e) {
var href = this.get('href');
if(isSameDomain(href)) {
e.preventDefault();
window.location.hash = href;
} else {
//let the browser do its thing...
}
});
We change the hash around because in most browsers, this will register another history location, allowing the forward and back buttons to work. It also allows someone to copy and paste a link. IE6 and 7, of course, don’t. For them, we manipulate an iframe, which will register history locations.
Most browsers provide a hashchange event, but again, IE6 and 7 fail to deliver. Thankfully, Matias Niemelä has added support for the hashchange event into MooTools’ Event system, browser compatibility and all. If you’re curious, you can read up on the different browser issues his effort solves.
Once I got that added to the page, and setup a listener, the listening function will run everytime a user clicks a relative link, since we change the hash in our click handler above.
window.addEvent('hashchange', function(hash) {
new Page(hash).load();});
I actually use a PageManager arbiter to handle Page loading, inserting, removing, and caching, but that isn’t necessary (though I recommend it).
To get you started, the load and insert methods of the Page class are shown below:
var Page = new Class({
Implements: [Options, Events],
options: {
container: 'Content'
},
initialize: function(url, options) {
this.setOptions(options);
this.url = url;
},
load: function() {
var that = this;
this.request = new Request({
url: url,
onSuccess: function(text) {
that.content = text;
that.insert();
},
onFailure: function() {
that.content = ERROR_404_MSG;
that.insert();
}
}).send();
return this;
},
insert: function() {
//no request means we've never called load()
if(!this.request) {
return this.load();
}
//dont insert until we're done loading
if(this.request.running) {
return this;
}
$(this.options.container).set('html', this.content);
this.fireEvent('insert');
return this;
}
});
I’ve also declared methods like remove and unload, which my PageManager calls. I’ll be experimenting with what kind of optimizations I get by removing the content into a documentFragment instead of disposing of the content rebuilding if the user clicks back. But that should help give a start to trying to do this yourself.
As a precursor, its very simple to make a website like this work without the Ajax, so that its still accessible and searchable. In your server side code, most JavaScript frameworks will send an extra header, X-Requested-With, with the value of XMLHttpRequest. You can check if that header has been sent, and if so, send only the content html, and if not, send the entire page instead. ↩
The CSS2 Opacity property doesn’t work in the current versions of Internet Explorer - through IE8. However, it has provided a way to achieve similar results with a different method, using IE’s filter property. Javascript frameworks usually work this in for you when you try to set an elements opacity in a cross-browser fashion.
It should come as no surprise that there are bugs that arise from this filter property. One is that the filter doesn’t cascade to children that have their position as anything besides static.
This has been talked about in several places all over the internets, and even recently in the MooTools User Group, but it seemed worthy of documenting if only for my own purposes. You can see for yourself with this demo at jsFiddle.
A way to fix this has been suggested multiple times as well: You can set the positioned element to inherit the filter. Unfortunately, the instance where I ran into this bug proved unsolvable, because I actually needed that filter property to do something besides inherit. Nonetheless, if you’re simply trying to fade out and find that part of it isn’t fading in IE, this will solve it. This, and apparently some sort of change to the layout of the element is also required. You could change its display to inline-block, but some felt that setting contentEditable to true to be the least intrusive on changing the layout. I’m not so sure I agree, but here is it anyways.
#relative {
position: relative;
filter: inherit;
}
Yesterday Jeff Atwood wrote about Fitts’ law and the contrapositive in regards to big, bad eject buttons. The point of his piece was that you shouldn’t have buttons with irreversibledestructiveresults be right next to buttons with more frequent use.
I was actually more intrigued by a couple of the commentors when they mentioned a real life example of excellent “eject” button UI: these incredibly important buttons have a safety cap over them.
Put a safety cap over the button. You know, like in the movies :-) You cannot press the button, because there is a “cap” on top of it. You first have to “open the cap” before you can press the button below it. In case of software, the button would be not directly visible, you first have to press somewhere to make the button appear and then you can press it
Yea, that. There’s no way you can accidentally push that button. (Wouldn’t it be nice if that were true?)
I’ve seen this idea before, I’m sure, but wanted to throw it together anyways. When you click the delete button, or any other important, damaging button, the delete button disappears and a slider appears, requiring you to move the slider over to complete the delete action. This only took a few minutes, so I wouldn’t necessary use this code for production. Regardless, here’s a proof of concept: (if you’re in a reader, you should come through to the site to see this).