Showing posts with label Style. Show all posts
Showing posts with label Style. Show all posts

Wednesday, August 13, 2008

javascript get style pixel value in ie6 and ie7


var PIXEL = /^\d+(px)?$/i;
function getPixelValue(element, value) {
if (PIXEL.test(value)) return parseInt(value);
var style = element.style.left;
var runtimeStyle = element.runtimeStyle.left;
element.runtimeStyle.left = element.currentStyle.left;
element.style.left = value || 0;
value = element.style.pixelLeft;
element.style.left = style;
element.runtimeStyle.left = runtimeStyle;
return value;
};

Reference: http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291

Friday, August 08, 2008

element.style.display in firefox3

在mac firefox3中element.style.display = "" 与 element.style.display = "none"一样,如果需要显示此element,则需要用element.style.display = "block"

Wednesday, January 23, 2008

value of element.style must be string in Javascript


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>img</title>
</head>
<body>
<div id="img" style="position: absolute;">
<img border="0" alt="img" src="logo.gif"/>
</div>
<script type="text/javascript">
var img = document.getElementById('img');
img.style.left = 200;
img.style.top = 200 + "px";
</script>
</body>
</html>

当文档声明DOCTYPE没有的话,JS会正常的起效果,如果加上第一行的文档声明,则JS中的left就不会生效.
参考JavaScript Pocket Reference, 2nd Edition中第3章Style:
Synopsis
element.style
Properties
The Style object defines a large number of properties: one property for each CSS attribute defined by the CSS2 specification. The property names correspond closely to the CSS attribute names, with minor changes required to avoid syntax errors in JavaScript. Multiword attributes that contain hyphens, such as font-family are written without hyphens in JavaScript, and each word after the first is capitalized: fontFamily. Also, the float attribute conflicts with the reserved word float, so it translates to the property cssFloat.

The visual CSS properties are listed in the following table. Since the properties correspond directly to CSS attributes, no individual documentation is given for each property. See a CSS reference (such as Cascading Style Sheets: The Definitive Guide (O'Reilly), by Eric A. Meyer) for the meaning and legal values of each. Note that current browsers do not implement all of these properties.

All of the properties are strings, and care is required when working with properties that have numeric values. When querying such a property, you must use parseFloat( ) to convert the string to a number. When setting such a property you must convert your number to a string, which you can usually do by adding the required units specification, such as "px".

Monday, September 24, 2007

网上看到的一个Ruby风格的JS源码


Object.prototype.clone = function(deepClone) {
var result = new this.constructor()
for (var property in this) {
if (deepClone && typeof(this[property]) == 'object') {
result[property] = this[property].clone(deepClone)
} else {
result[property] = this[property]
}
}
return(result)
}

Object.prototype.extend = function(other) {
if (!this.mixins) this.mixins = []
this.mixins.push(other)
for (var property in other)
if (!this.hasOwnProperty(property))
this[property] = other[property]
}

Object.prototype.cmp = function(other) {
if (this < other) return(-1)
if (this > other) return(+1)
return(0)
}

Object.prototype.valuesAt = function() {
var obj = this
return(arguments.toArray().map(function(index) {
return(obj[index])
}))
}

Object.prototype.toArray = function() {
if (!this.length) throw("Can't convert")
var result = []
for (var i = 0; i < this.length; i++)
result.push(this[i])
return(result)
}

Object.prototype.hash = function() {
return(this.toSource().hash())
}

Object.prototype.instanceOf = function(klass) {
return(this.constructor == klass)
}

Object.prototype.isA = Object.prototype.kindOf = function(klass) {
if (this.instanceOf(klass)) return(true)
if (this["mixins"] != undefined && this.mixins.includes(klass))
return(true)
return(false)
}

Object.prototype.methods = function() {
var result = []
for (var property in this)
if (typeof(this[property]) == "function")
result.push(property)
return(result)
}

Object.prototype.respondTo = function(method) {
return(this.methods().includes(method))
}

Object.prototype.send = function(method) {
var rest = arguments.toArray().last(-1)
if (!this.respondTo(method)) throw("undefined method")
return(this[method].apply(this, rest))
}

Object.prototype.instanceEval = function(code) {
if (code.isA(Function))
return(code.apply(this))
else
return(eval(code.toString()))
}

Number.prototype.times = function(block) {
for (var i = 0; i < this; i++) block(i)
}

Number.prototype.upto = function(other, block) {
for (var i = this; i <= other; i++) block(i)
}

Number.prototype.downto = function(other, block) {
for (var i = this; i >= other; i--) block(i)
}

Number.prototype.towards = function(other, block) {
var step = this.cmp(other)
for (var i = this; i !== other - step; i -= step)
block(i)
}

Number.prototype.succ = function() { return(this + 1) }
Number.prototype.pred = function() { return(this - 1) }

Number.prototype.chr = function() { return(String.fromCharCode(this)) }

enumerable = new Object()
enumerable.eachWindow = function(window, block) {
if (!window.isA(Range)) window = range(0, window)
elements = [], pushed = 0
this.each(function(item, index) {
elements.push(item)
pushed += 1
if (pushed % window.rend == 0) {
start = [0, window.start - window.rend + pushed].max()
end = [0, window.rend + pushed].max()
block(elements.fetch(xrange(start, end)), index)
}
})
}

enumerable.collect = enumerable.map = function(block) {
var result = []
this.each(function(item, index) {
result.push(block(item, index))
})
return(result)
}

enumerable.toArray = enumerable.entries = function() {
return(this.map(function(item) { return(item) }))
}

enumerable.inject = function(firstArg) {
var state, block, first = true
if (arguments.length == 1) {
block = firstArg
} else {
state = firstArg
block = arguments[1]
}
this.each(function(item, index) {
if (first && typeof(state) == "undefined")
state = item, first = false
else
state = block(state, item, index)
})
return(state)
}

enumerable.find = enumerable.detect = function(block) {
var result, done
this.each(function(item, index) {
if (!done && block(item, index)) {
result = item
done = true
}
})
return(result)
}

enumerable.findAll = enumerable.select = function(block) {
return(this.inject([], function(result, item, index) {
return(block(item, index) ? result.add(item) : result)
}))
}

enumerable.grep = function(obj) {
return(this.findAll(function(item) {
return(obj.test(item))
}))
}

enumerable.reject = function(block) {
return(this.select(function(item, index) {
return(!block(item, index))
}))
}

enumerable.compact = function() {
return(this.select(function(item) {
return(typeof(item) != "undefined")
}))
}

enumerable.nitems = function() { return(this.compact().length) }

enumerable.sortBy = function(block) {
return(this.map(function(item, index) {
return([block(item, index), item])
}).sort(function(a, b) {
return(a[0].cmp(b[0]))
}).map(function(item) {
return(item[1])
}))
}

enumerable.all = function(block) {
return(this.findAll(block).length == this.length)
}

enumerable.any = function(block) {
return(typeof(this.find(block)) != "undefined")
}

enumerable.includes = function(obj) {
return(this.any(function(item) {
return(item === obj)
}))
}

enumerable.index = function(obj) {
var result
this.find(function(item, index) {
if (obj == item) {
result = index
return(true)
} else {
return(false)
}
})
return(result)
}

enumerable.uniq = function() {
return(this.inject([], function(result, item) {
return(result.includes(item) ? result : result.add(item))
}))
}

enumerable.max = function(block) {
if (!block) block = function(a, b) { return(a.cmp(b)) }
return(this.sort(block).last())
}

enumerable.min = function(block) {
if (!block) block = function(a, b) { return(a.cmp(b)) }
return(this.sort(block).first())
}

enumerable.partition = function(block) {
var positives = [], negatives = []
this.each(function(item, index) {
if (block(item, index))
positives.push(item)
else
negatives.push(item)
})
return([positives, negatives])
}

enumerable.zip = function() {
var ary = arguments.toArray()
ary.unshift(this)
return(ary.transpose())
}

enumerable.flatten = function(depth) {
if (depth == undefined) depth = -1
if (!depth) return(this)
return(this.inject([], function(result, item) {
var flatItem = item.respondTo("flatten") ? item.flatten(depth - 1) : [item]
return(result.merge(flatItem))
}))
}

Array.fromObject = function(obj) {
if (!obj.length) throw("Can't convert")
var result = []
for (var i = 0; i < obj.length; i++)
result.push(obj[i])
return(result)
}

Array.prototype.transpose = function() {
var result, length = -1
this.each(function(item, index) {
if (length < 0) { /* first element */
length = item.length
result = Array.withLength(length, function() {
return(new Array(this.length))
})
} else if (length != item.length) {
throw("Element sizes differ")
}
item.each(function(iitem, iindex) {
result[iindex][index] = iitem
})
})
return(result)
}

Array.withLength = function(length, fallback) {
var result = [null].mul(length)
result.fill(fallback)
return(result)
}

Array.prototype.each = function(block) {
for (var index = 0; index < this.length; ++index) {
var item = this[index]
block(item, index)
}
return(this)
}
Array.prototype.extend(enumerable)

Array.prototype.isEmpty = function() { return(this.length == 0) }

Array.prototype.at = Array.prototype.fetch = function(index, length) {
if (index.isA(Range)) {
var end = index.rend + (index.rend < 0 ? this.length : 0)
index = index.start
length = end - index + 1
}
if (length == undefined) length = 1
if (index < 0) index += this.length
var result = this.slice(index, index + length)
return(result.length == 1 ? result[0] : result)
}

Array.prototype.first = function(amount) {
if (amount == undefined) amount = 1
return(this.at(xrange(0, amount)))
}

Array.prototype.last = function(amount) {
if (amount == undefined) amount = 1
return(this.at(range(-amount, -1)))
}

Array.prototype.store = function(index) {
var length = 1, obj
arguments = arguments.toArray()
arguments.shift()
if (arguments.length == 2)
length = arguments.shift()
obj = arguments.shift()
if (!obj.isA(Array)) obj = [obj]
if (index.isA(Range)) {
var end = index.rend + (index.rend < 0 ? this.length : 0)
index = index.start
length = end - index + 1
}
if (index < 0) index += this.length
this.replace(this.slice(0, index).merge(obj).merge(this.slice(index + length)))
return(this)
}

Array.prototype.insert = function(index) {
var values = arguments.toArray().last(-1)
if (index < 0) index += this.length + 1
return(this.store(index, 0, values))
}

Array.prototype.update = function(other) {
var obj = this
other.each(function(item) { obj.push(item) })
return(obj)
}

Array.prototype.merge = Array.prototype.concat
Array.prototype.add = function(item) { return(this.merge([item])) }

Array.prototype.clear = function() {
var obj = this
this.length.times(function(index) {
delete obj[index]
})
this.length = 0
}

Array.prototype.replace = function(obj) {
this.clear()
this.update(obj)
}

Array.prototype.mul = function(count) {
var result = []
var obj = this
count.times(function() { result = result.merge(obj) })
return(result)
}

Array.prototype.fill = function(value) {
var old_length = this.length
var obj = this
this.clear()
var block
if (typeof(value) != "function")
block = function() { return(value) }
else
block = value

old_length.times(function(i) {
obj.push(block(i))
})
}

Array.prototype.removeAt = function(targetIndex) {
var result = this[targetIndex]
var newArray = this.reject(function(item, index) {
return(index == targetIndex)
})
this.replace(newArray)
return(result)
}

Array.prototype.remove = function(obj) {
this.removeAt(this.index(obj))
}

Array.prototype.removeIf = function(block) {
this.replace(this.reject(block))
}

function Range(start, end, excludeEnd) {
this.begin = this.start = start
this.end = end
this.excludeEnd = excludeEnd
this.rend = excludeEnd ? end.pred() : end
this.length = this.toArray().length
}

function range(start, end) { return(new Range(start, end)) }
function xrange(start, end) { return(new Range(start, end, true)) }

Range.prototype.toString = function() {
return("" + this.start + (this.excludeEnd ? "..." : "..") + this.end)
}

Range.prototype.each = function(block) {
var index = 0
this.start.towards(this.rend, function(i) {return(block(i, index++))})
}
Range.prototype.extend(enumerable)

Range.prototype.includes = function(item) {
return(this.start.cmp(item) == -1 && this.rend.cmp(item) == +1)
}

function Hash(defaultBlock) {
this.defaultBlock = defaultBlock
this.keys = []
this.values = []
this.length = 0
}

Hash.fromArray = function(array) {
var result = new Hash()
array.each(function(item) {
var key = item[0], value = item[1]
result.store(key, value)
})
return(result)
}

Hash.prototype.at = Hash.prototype.fetch = function(key, block) {
var result
if (this.hasKey(key))
result = this["item_" + key.hash()]
else {
if (block)
result = block(key)
else
result = defaultBlock(key)
}
return(result)
}

Hash.prototype.store = function(key, value) {
this.keys.push(key)
this.values.push(value)
this.length++
return(this["item_" + key.hash()] = value)
}

Hash.prototype.toA = function() {
return(this.keys.zip(this.values))
}

Hash.prototype.isEmpty = function() {
return(this.length == 0)
}

Hash.prototype.has = Hash.prototype.includes = Hash.prototype.hasKey = function(key) {
return(hasOwnProperty("item_" + key.hash()))
}

Hash.prototype.hasValue = function(value) {
return(this.values.includes(value))
}

Hash.prototype.each = function(block) {
this.toA().each(function (pair) {
return(block(pair[1], pair[0]))
})
}

Hash.prototype.extend(enumerable)

Hash.prototype.merge = function(other) {
other.each(function(value, key) {
this.store(key, value)
})
}

Hash.prototype.remove = function(key) {
var valueIndex = this.keys.index(key)
var value = this.values[valueIndex]
this.keys.remove(key)
this.values.removeAt(valueIndex)
delete(this["item_" + key.hash()])
this.length--
return([key, value])
}

Hash.prototype.removeIf = function(block) {
this.each(function(value, key) {
if (block(value, key))
this.remove(key)
})
}

Hash.prototype.shift = function() {
return(this.remove(this.keys[0]))
}

Hash.prototype.clear = function() {
var obj = this
this.length.times(function() {obj.shift()})
}

Hash.prototype.replace = function(obj) {
this.clear()
this.merge(obj)
}

Hash.prototype.invert = function() {
return(Hash.fromArray(this.map(function(value, key) {
return([value, key])
})))
}

Hash.prototype.rehash = function() {
var result = new Hash(this.defaultBlock)
this.each(function(value, key) {
result.store(key, value)
})
this.replace(result)
}

function MatchData(matches, str, pos) {
this.matches = matches, this.string = str
this.begin = this.position = pos
this.match = matches[0]
this.captures = matches.slice(1)
this.end = pos + this.match.length
this.length = matches.length
this.preMatch = str.substr(0, pos)
this.postMatch = str.substr(this.end)
}

MatchData.prototype.toString = function() { return(this.match) }
MatchData.prototype.at = function(index) {
return(this.matches.at(index))
}
MatchData.prototype.toArray = function() { return(this.matches) }

RegExp.prototype.match = function(str) {
var matches
if (matches = this.exec(str)) {
var pos = str.search(this)
return(new MatchData(matches, str, pos))
}
}

String.prototype.clone = function() { return(new String(this)) }

String.prototype.each = function(block) {
this.split("\n").each(block)
}

String.prototype.extend(enumerable)

String.prototype.toArray = function() { return(this.split("\n")) }

String.prototype.towards = function(other, block) {
var item = this
while (item.cmp(other) <= 0) {
block(item)
item = item.succ()
}
}

String.prototype.hash = function() {
var result = 0
this.split("").each(function(item) {
result += item.charCodeAt(0)
result += (result << 10)
result ^= (result >> 6)
})
result += (result << 3)
result ^= (result >> 11)
result += (result << 15)
return(result)
}

String.prototype.chars = function() { return(this.split("")) }

String.prototype.at = String.prototype.fetch = function(index, length) {
if (index.isA(Range)) {
var end = index.rend + (index.rend < 0 ? this.length : 0)
index = index.start
length = end - index + 1
}
if (length == undefined) length = 1
if (index < 0) index += this.length
return(this.substr(index, length))
}

String.prototype.store = String.prototype.change = function(index) {
var length = 1, obj
arguments = arguments.toArray()
arguments.shift()
if (arguments.length == 2)
length = arguments.shift()
obj = arguments.shift()
if (index.isA(Range)) {
var end = index.rend + (index.rend < 0 ? this.length : 0)
index = index.start
length = end - index + 1
}
if (index < 0) index += this.length
return(this.substr(0, index) + obj + this.substr(index + length))
}

String.prototype.reverse = function() {
return(this.split("").reverse().join(""))
}

String.prototype.scan = function(pattern) {
var str = this, result = [], oldPos = -1, match, offset = 0
while (match = pattern.match(str)) {
if (match.end == match.begin)
throw("Can't have null length matches with scan()")
var newMatch = new MatchData(match.matches, match.string, match.position + offset)
result.push(newMatch)
str = match.postMatch
offset += match.toString().length
}
return(result)
}

String.prototype.sub = function(what, by, global) {
var block = typeof(by) == "function" ? by : function() { return(by) }
var matches = this.scan(what), result = this, offset = 0
if (!global && !by.global) matches = matches.slice(0, 1)
matches.each (function(match) {
var replacement = block(match)
offset += replacement.length - match.toString().length
result = result.change(match.begin + offset, match.toString().length, replacement)
})
return(result)
}
String.prototype.gsub = function(what, by) { return(this.sub(what, by, true)) }

String.prototype.tr = function(from, to) {
var map = Hash.fromArray(from.chars().zip(to.chars()))
return(this.chars().map(function(chr) {
return(map.includes(chr) ? map.fetch(chr) : chr)
}).join(""))
}

String.prototype.mul = function(other) {
var result = "", str = this
other.times(function() { result += str })
return(result)
}

String.prototype.isUpcase = function() { return(this == this.upcase()) }
String.prototype.isDowncase = function() { return(this == this.downcase()) }
String.prototype.isCapitalized = function() {
return(this.fetch(0).isUpcase() && this.fetch(range(1, -1)).isDowncase())
}
String.prototype.upcase = String.prototype.toUpperCase
String.prototype.downcase = String.prototype.toLowerCase
String.prototype.capitalize = function() {
return(this.fetch(0).upcase() + this.fetch(range(1, -1)).downcase())
}
String.prototype.swapcase = function() {
return(this.chars().map(function(chr) {
if (chr.isUpcase()) return(chr.downcase())
if (chr.isDowncase()) return(chr.upcase())
return(chr)
}).join(""))
}
String.prototype.ord = function() { return(this.charCodeAt(0)) }

String.prototype.isEmpty = function() { return(this.length == 0) }

String.prototype.succ = function() {
if (this.isEmpty()) return(this)
/* numerics */
if (/^\d+$/.test(this))
return((Number(this) + 1).toString())
/* just one character */
if (this.length == 1) {
/* letters */
if (/[A-Za-z]/.test(this)) {
var lastLetter = this.isUpcase() ? 'Z' : 'z'
var firstLetter = this.isUpcase() ? 'A' : 'a'
return((this == lastLetter) ? firstLetter.mul(2) : (this.ord() + 1).chr())
} else {
return(this == (-1).chr() ? 0.0.chr().mul(2) : (this.ord() + 1).chr())
}
/* multiple characters */
} else {
var result = this
for (var index = this.length; index >= 0; index--) {
var chr = this.at(index)
if (chr.succ().length == 1 || index == 0)
return(result.change(index, chr.succ()))
else
result = result.change(index, chr.succ().at(-1))
}
}
}

String.prototype.ljust = function(length, fill) {
if (!fill) fill = " "
if (fill.length > 1) throw("TODO: Make fills with length > 1 work.")
return(this + fill.mul(length / fill.length - this.length))
}


Thursday, September 20, 2007

The Elements of JavaScript Style (Part One)

Part One


Douglas Crockford

The Department of Style


2005-09-19


Programming is difficult. At its core, it is about managing complexity. Computer
programs are the most complex things that humans make. Quality is a illusive
and elusive.


Good architecture is necessary to give programs enough structure to be able to grow
large without collapsing into a puddle of confusion, but the ways in which we
express the details of a program are equally important. A program's true nature
can be concealed by sloppy coding. Only when the presentation of a program is
clear can we have any hope of reasoning correctly about its efficiency, or security,
or correctness.


The classic work in literary style is William Strunk's The
Elements of Style
, a skinny manual on writing in English, with advice on
usage, composition, and form. The idea of style was applied unsuccessfully to
programming in Kreitzberg and Shneiderman's The
Elements of FORTRAN Style
in 1972, and then brilliantly in Kernighan and
Plauger's The Elements of Programming Style in 1978:




Good programming cannot be taught by preaching generalities. The way to learn
to program well is by seeing, over and over, how real programs can be improved
by the application of a few principles of good practice and a little common
sense.



They took programs culled from other programming textbooks, which they criticized
and improved.


When we talk of style here, we are not talking about fads and fashions,
nor are we talking about CSS or conventions of layout or typography. We are
talking about timeless qualities of expression which can substantially increase
the value of a codebase. For companies whose valuations are are inextricably
bound to their codebases, style should be a vital concern.


We use many programming languages, but in a way, JavaScript is the most important.
It is the language of the browser. When people come to our site, they are (perhaps
unknowingly) inviting our JavaScript programs to execute in their machines.
We have a special obligation to make those programs good.


There are no good texts on JavaScript
programming. Most of the people on the web who are producing JavaScript programs
learned it by copying really bad examples from bad books, bad websites, and
bad tools. We have
an amazingly good community of JavaScript programmers here, but still we can
benefit from better practice of style.



To demonstrate this, I will be taking programs from our public website, and
showing how they can be improved. It is not my intention to embarrass anyone.
My intention is only to show the value of style by example. I will be revealing
no secrets: I will be showing you what we are already transmitting to everyone
in the world.


 


The following examples were extracted from www.yahoo.com
on 2005-09-19.


<script language=javascript><!--
lck='',
sss=1127143538,
ylp='p.gif?t=1127143538&_ylp=A0Je5ipy2C5D54AAwVX1cSkA',
_lcs='';
--></script>

This script block uses the language attribute. This was a feature
that was introduced by Microsoft in order to support VBScript. Netscape then
adopted it to support its own nonstandard deviations. W3C did not adopt the
language attribute, favoring instead a type attribute
which takes a MIME type. Unfortunately, the MIME type was not standardized,
so it is sometimes "text/javascript" or "application/ecmascript"
or something else. Fortunately, all browsers will always choose JavaScript as
the default programming language, so it is always best to simply write <script>.
It is smallest, and it works on the most browsers.



The use of HTML comments in scripts dates further back to a transitional problem
between Netscape Navigator and Netscape Navigator 2. The latter introduced the
<script> tag. However, users of the former would see the
script as text because of the HTML convention that unrecognized markup is ignored.
The <!-- comment hack stopped being necessary by the time Netscape
Navigator 3 came out. It certainly is not needed now. It is ugly and a waste
of space.


The comma operator was borrowed, like much of JavaScript's syntax, from C.
The comma operator takes two values and returns the second one. Its presence
in the language definition tends to mask certain coding errors, so compilers
tend to be blind to some mistakes. It is best to avoid the comma operator, and
use the semicolon statement separator instead.


In this case, we are defining some global variables. JavaScript, when assigning
to an unknown variable, creates a new global variable instead of generating
an error. This was, in hindsight, a mistake. It is best to avoid mistakes, even
when they are standard mistakes. We should be explicit in declaring the variables.
It will cost us 4 characters, but it is the right thing to do.



<script>

var lck = '3ek6b0i2he2a5eh3/o',
sss = 1126894256,
ylp = 'p.gif?t=1126894256&_ylp=A0Je5iOwCitDw2YBX331cSkA',
_lcs = '94040';
</script>

From that we can derive this principle:




Avoid archaic constructions.




The next example looks at a cookie class constructor. It creates an object
having a get method and a set method.



function yg_cookie() {
this.get = function (n) {
var s,
e,
v = '',
c = ' ' + document.cookie + ';';
if ((s = c.indexOf((' ' + n + '='))) >= 0) {
if ((e = c.indexOf(';',s)) == -1)
e = c.length;
s += n.length + 2;
v = unescape(c.substring(s, e));
}
return (v);
}
this.set = function (n,v,e) {
document.cookie = n + "=" + escape(v) +
";expires=" + (new Date(e * 1000)).toGMTString() +
";path=/" + ";domain=www.yahoo.com";
}
}
var _yc = new yg_cookie();


JavaScript's if statement is similar to C's: it can take statements
or blocks. The problem with using statements is that a common error is very
difficult to detect. It is better to write


if ((e = c.indexOf(';', s)) == -1) 
e = c.length;

as


if ((e = c.indexOf(';', s)) == -1) {
e = c.length;
}

The use of blocks avoids situations like this:


if ((e = c.indexOf(';', s)) == -1) 
e = c.length;
s += n.length + 2;

It might appear that s is only incremented when indexOf
returns -1, but this is not the case. Bugs like that can be very
expensive to find, but can be inexpensively avoided by always using braces to
indicate structure.





Always use blocks in structured statements.




Another bad habit that JavaScript inherited from C is the assignment expression.
It appears to streamline code, but it can make control flow more difficult to
understand. The get method gets clearer if we separate the computation
of s and e from their uses.


this.get = function (n) {
var v = '',
c = ' ' + document.cookie + ';',
s = c.indexOf((' ' + n + '=')),
e = c.indexOf(';', s);
if (s >= 0) {
if (e == -1) {
e = c.length;
}
s += n.length + 2;
v = unescape(c.substring(s, e));
}
return (v);
}


We can now see that there are excess parens around the argument to indexOf
where s is computed. (There are also unnecessary parens in the
return statement.) But more importantly, it is easier to see what
the purpose of if (e == -1) is: If a final semicolon is not found
in the cookie, then assume that the cookie ends at the end of the string. However,
when we computed c, we appended a semicolon to the cookie, which
guarantees that the condition the if is anticipating will never
happen. So we can remove the if.





Avoid assignment expressions.




When a function is assigned to a value, as in this.get = function (n)
{
... } it should end with a semicolon just like all assignment
statements.


function yg_cookie() {
this.get = function (n) {
var v = '',
c = ' ' + document.cookie + ';',
s = c.indexOf((' ' + n + '='));
if (s >= 0) {
s += n.length + 2;
v = unescape(c.substring(s, c.indexOf(';', s)));
}
return v;
};
this.set = function (n,v,e) {
document.cookie = n + "=" + escape(v) +
";expires=" + (new Date(e * 1000)).toGMTString() +
";path=/" + ";domain=www.yahoo.com";
};
}
var _yc = new yg_cookie();


Finally, we see that yg_cookie is a constructor that produces
a single stateless object. We do not need a constructor function at all. We
can simply make an empty object and augment it by assigning the methods to it.


var _yc = new Object();
_yc.get = function (n) {
var v = '',
c = ' ' + document.cookie + ';',
s = c.indexOf((' ' + n + '='));
if (s >= 0) {
s += n.length + 2;
v = unescape(c.substring(s, c.indexOf(';', s)));
}
return v;
};
_yc.set = function (n,v,e) {
document.cookie = n + "=" + escape(v) +
";expires=" + (new Date(e * 1000)).toGMTString() +
";path=/" + ";domain=www.yahoo.com";
};


If we do not need to support Netscape 3 or IE 4, then we can do that more elegantly
by using the object literal notation.


var _yc = {
get: function (n) {
var v = '',
c = ' ' + document.cookie + ';',
s = c.indexOf((' ' + n + '='));
if (s >= 0) {
s += n.length + 2;
v = unescape(c.substring(s, c.indexOf(';', s)));
}
return v;
},
set: function (n,v,e) {
document.cookie = n + "=" + escape(v) +
";expires=" + (new Date(e * 1000)).toGMTString() +
";path=/" + ";domain=www.yahoo.com";
}
};




Use object augmentation.




At this point we have a couple of methods for manipulating cookies. It is surprising
then that the very next thing we find is code that does cookie manipulation
without taking advantage of the methods we just defined.


var b,
l = '',
n = '0',
y;
y = ' ' + document.cookie + ';';
if ((b = y.indexOf(' Y=v')) >= 0) {
y = y.substring(b, y.indexOf(';', b)) + '&';
if ((b = y.indexOf('l=')) >= 0) {
l = y.substring(b + 2, y.indexOf('&', b));
if ((b = y.indexOf('n=')) >= 0)
n = y.substring(b + 2, y.indexOf('&', b));
}
}


It even replicates the same techniques that we saw earlier. It is likely that
both chunks of code were adapted from the same faulty original. We can improve
it by taking advantage of our recent work:


var l = '',
n = '0',
y = _yc.get('Y') + '&',
b = y.indexOf('l=');
if (b >= 0) {
l = y.substring(b + 2, y.indexOf('&', b));
b = y.indexOf('n=');
if (b >= 0) {
n = y.substring(b + 2, y.indexOf('&', b));
}
}

Code reuse is the Holy Grail of Software Engineering. We can imagine great
efficiencies obtained by avoiding the vast amount of hand work required by the
current state of the art. Here we found a failure to use a method that had been
defined adjacent to the place where it was needed.




Use common libraries.




The structure of software systems tend to reflect the structure of the organizations
that produce them. In this case, we see evidence of obvious inefficiencies caused
by an organization that lacks awareness of the interconnectedness of its own
processes. The application of style is critical, because it is only possible
to fit the pieces together properly if we can understand what the pieces are.


原文: The Elements of JavaScript Style

The Elements of JavaScript Style (Part Two)

Part Two: Idioms


Douglas Crockford

The Department of Style


2005-09-21


There are idioms that we can use to make our intentions clearer and more concise.


Consider this function:


function gw(f) {
if (d.w.sv.checked == true) {
zv = 'on';
} else {
zv = 'off';
}
procframe.location.replace("http://b.www.yahoo.com/module/wtr_tr.php?p=" +
escape(f.p.value) + "&sv=" + zv);
return false;
}

The == operator should not be used for comparing values with true
because it does type coercion. If our intent is to determine if d.w.sv.checked
is the boolean value true, then we must use the ===
operator. If we only care that a value is truthy (and not falsy)
then it is better to not use an equality operator at all.



For example, because of type coercion., 1 == true is true, but
1 === true
is false. The == operator can hide type errors.




Watch out for type coercion when using ==.





The if statement is being used to select one of two values. This
is what the ?: ternary operator is for.


zv = d.w.sv.checked ? 'on' : 'off';



Use the ?: operator to select one of two values.





The variable zv is not declared as a var or parameter of this
function, so it is an implicit global variable. If there is another function
on this page that uses a similarly named global variable, then a failure could
result. Bugs like this can be very difficult to find but are very easily avoided.
In this case, we can either declare that zv as a var, or we can
notice that it is used only once and get rid of it entirely.


function gw(f) {
procframe.location.replace("http://b.www.yahoo.com/module/wtr_tr.php?p=" +
escape(f.p.value) + "&sv=" + d.w.sv.checked ? 'on' : 'off');
return false;
}



Never use implicit global variables.





We would normally be suspicious of functions that return a constant, but this
is something that is sometimes required in a browser environment.



Next we see a case where the ?: operator is used improperly. It
is being used to select between two assignments.


function u(o, z) {
var em = o.id.substring(1);
var p = d.getElementById('e' + em);
if (p) {
(z == 0) ? p.style.backgroundColor = '#fff' :
p.style.backgroundColor = '#989898';
}
p = d.getElementById('e' + (em - 1));
if (p) {
(z == 0) ? p.style.backgroundColor = '#fff' :
p.style.backgroundColor = '#989898';
}
}

The test of z is ambiguous. Do we select color #fff
if z is exactly 0, or if z is falsy?
As stated it appears to indicate the former, but it actually means the latter.
Fortunately in this case, we probably intend the latter, so it is not technically
an error (this time). But it is bad stylistically.



We can replace the ?: with an if, but it happens
that the assignments all use the same lvalue, so this time we can make
the correction without using an if.


function u(o, z) {
var em = o.id.substring(1),
p = d.getElementById('e' + em);
if (p) {
p.style.backgroundColor = z ? '#fff' : '#989898';
}
p = d.getElementById('e' + (em - 1));
if (p) {
p.style.backgroundColor = z ? '#fff' : '#989898';
}
}




Do not use the ?: operator to select one of
two actions.




Event handling suffers from browser dependencies. Ideally, application programs
should be insulated from browser deficiencies by common libraries. When such
libraries are not available, functions like this happen:


function md(e) {
(window.event) ? ev = window.event : ev = e;
(ev.target) ? sr = ev.target : sr = ev.srcElement;
if (ev && sr && sr.id == "fp" || sr.id == "sb") st = 1;
if (sr.className.indexOf("pllist") < 0 && sr.className != "more" &&
sr.className != "plinkc" && sr.tagName != "scrollbar " &&
_toClose && _toCloseNorgie) {
d.getElementById(_toClose).innerHTML = "";
_toClose = "";
_toCloseNorgie.parentNode.className = '';
_toCloseNorgie = '';
}
}


Some browsers pass an event object to event handlers as a parameter. Microsoft
chose instead to put the event object in a global event variable.
In JavaScript, global variables are members of the global object. In browsers,
the global object always contains a window member whose value is
the global object. Accessing global variables through window is
a way of avoiding undefined variable errors when testing for the existence of
a variable. However, it should never be necessary to make such a test.


Instead of first determining if this is a Microsoft event, we can instead ask
if it is the other kind.


ev = e || event;

We used the || (default) operator. If e
is truthy, we will use its value, but if e is falsy then we will
use event.



In the next statement, we can again use the || operator to determine
sr, the source element or target.


We should make ev and sr vars to avoid global conflict.


function md(e) {
var ev = e || event,
sr = ev.target || ev.srcElement;
if (sr && (sr.id == 'fp' || sr.id == 'sb')) {
st = 1;
}
if (sr.className.indexOf('pllist') < 0 && sr.className != 'more' &&
sr.className != 'plinkc' && sr.tagName != 'scrollbar ' &&
_toClose && _toCloseNorgie) {
d.getElementById(_toClose).innerHTML = '';
_toClose = '';
_toCloseNorgie.parentNode.className = '';
_toCloseNorgie = '';
}
}




Use the || operator to specify a default value.




Next we find another event handler. As you would expect, it repeats some of
the same stylelessness as the previous one.


function kd(e) {
(window.event) ? ev = window.event : ev = e;
(ev.target) ? el = ev.target : el = ev.srcElement;
if (ev && el) {
code = ev.keyCode;
id = el.id;
} else {
return;
}
ctn = lt.id.substring(1);
if (code == 13) {
return;
} else if ((code == 191 || code == 222) && id != 'fp') {
_ffs = 1;
gk = 0;
} else if ((code < 31 || code > 41) &&
(code < 16 || code > 18) && code != 9 && code != 8) {
gk = 1;
} else {
gk = 0;
}
if (!_ffs && (id == 'fp' || id == 'st')) {
if (code == 9) {
if (box.value == '' || (box.value != '' && (at == 1 || ev.shiftKey))) {
mt(ctn);
} else if (id == 'st' && box.value != '' && at == 0) {
at = 1;
mt(ctn);
}
} else if (id == 'fp' && gk == 0 &&
(box.value == '' && st == 0) && !ev.shiftKey && !ev.ctrlKey && !ev.altKey) {
d.getElementById('mk').focus();
d.getElementById('mk').blur();
} else if (gk == 1) {
at = 0;
}
} else if ((id == 'mk2' && box.value != '' && ev.shiftKey && code == 9) ||
(id == 'm6' && !ev.shiftKey && code == 9)){
d.getElementById('mk').focus();
} else if (!_ffs && gk == 1 && el.type != 'text' && !ev.ctrlKey && !ev.altKey){
box.value = '';
box.focus();
}
}
function mt(ctn) {
if ((ev && !ev.ctrlKey && !ev.altKey) || !ev) {
if (ev.shiftKey){
nextTab = parseInt(ctn) - 1;
} else {
nextTab = parseInt(ctn) + 1;
}
if (nextTab == 0) {
d.getElementById('mk').focus();
} else if (nextTab < 8) {
t(d.getElementById('v' + nextTab));
} else {
return;
}
}
}


What is interesting is that it has a companion function, mt, which
is only called from kd. mt is passed one parameter
(ctn), but most of the communication between kd and
mt is through global variables.





Global variables are evil.




We could eliminate the use of global variables by increasing the number of
parameters sent to mt. But instead, we will make mt
an inner function of kd. As an inner function, mt
would have access to all of kd's vars.



function kd(e) {
var ev = e || event,
el = ev.target || ev.srcElement,
cnt,
code = ev.keyCode,
gk,
id = el.id,
ctn = lt.id.substring(1);

function mt() {
var nextTab;
if (!ev.ctrlKey && !ev.altKey) {
nextTab = parseInt(ctn) + ev.shiftKey ? -1 : 1;
if (!nextTab) {
d.getElementById('mk').focus();
} else if (nextTab < 8) {
t(d.getElementById('v' + nextTab));
}
}
}

if (code == 13) {
return;
} else if ((code == 191 || code == 222) && id != 'fp') {
_ffs = 1;
gk = 0;
} else if ((code < 31 || code > 41) &&
(code < 16 || code > 18) && code != 9 && code != 8) {
gk = 1;
} else {
gk = 0;
}
if (!_ffs && (id == 'fp' || id == 'st')) {
if (code == 9) {
if (box.value == '' ||
(box.value != '' && (at == 1 || ev.shiftKey))) {
mt();
} else if (id == 'st' && box.value != '' && at == 0) {
at = 1;
mt();
}
} else if (id == 'fp' && gk == 0 && (box.value == '' && st == 0) &&
!ev.shiftKey && !ev.ctrlKey && !ev.altKey) {
d.getElementById('mk').focus();
d.getElementById('mk').blur();
} else if (gk == 1) {
at = 0;
}
} else if ((id == 'mk2' && box.value != '' && ev.shiftKey && code == 9) ||
(id == 'm6' && !ev.shiftKey && code == 9)){
d.getElementById('mk').focus();
} else if (!_ffs && gk == 1 && el.type != 'text' && !ev.ctrlKey &&
!ev.altKey) {
box.value = '';
box.focus();
}
}


Function mt is called from two places in kd. By making
it an inner function, we were able to significantly reduce the number of global
variables that kd uses, which reduces its likelihood of interfering
with other components. kd is still a mess, but it is now a slightly
less disorderly mess.




Use inner functions to avoid global variables.


原文The Elements of JavaScript Style

Sunday, September 16, 2007

ActiveRecord style Javascript REST

Jester 1.2: Flexible REST

This release is about making Jester more flexible, and better supporting custom REST APIs. The flurry of activity in ActiveResource is a good reminder that REST isn’t just several default controller actions—it’s a guiding philosophy to defining your own API. REST is just about using simple URLs and HTTP status codes to carry all the metadata, so that the bodies of your requests and responses don’t have to.

New features this release:

find() will take a hash of query parameters to append to the URL.
Base.model() takes all parameters after the name as a hash, instead of a list.
Asynchronous calls can take a hash of callbacks and options, instead of just a callback.
Support for reading an object’s schema. (new.xml)
Dates are parsed into Date objects. (Thanks to Nicholas Barthelemy)
Objects are reloaded from XML if XML is returned after a create or update request.
Included ObjTree.js inline, and replaced prototype.js with an optional lighter form (prototype.jester.js).

You can pass arbitrary query parameters along with a find request, in a hash. It’s the second parameter, bumping the asynchronous callback/options parameter to third. This breaks backwards compatibility.

>>> User.find("all", {admin: true, toys: 5})
GET http://localhost:3000/users.xml?admin=true&toys=5

Arguments when defining a model are now taken as a hash instead of a plain ordered list. This breaks backwards compatibility.

>>> Base.model("User", {plural: "people", prefix: "http://www.thoughtbot.com"})
>>> User._plural_url()
"http://www.thoughtbot.com/people.xml"

ou can now supply a hash of options to be fed directly to Prototype’s Ajax.Request method, instead of just a callback. You can use this to specify different callbacks for success or failure conditions, or override the HTTP method used in the request, or anything specified here. If you supply only a callback, it will be treated as your “onComplete” callback. You can use these options with a synchronous request if you set “asynchronous” to false.

>>> User.find(1, {}, {onSuccess: success, on404: notFound, method: "post"})
POST http://localhost:3000/users/1.xml

>>> User.find(1, {}, successCallback)
GET http://localhost:3000/users/1.xml

>>> eric = User.find(1, {}, {asynchronous: false})
GET http://localhost:3000/users/1.xml
There is a longstanding problem in Jester, which is that when you create or build a new object, you have to specify all of its properties in the attributes hash. If you simply call eric = User.build(), and then later, eric.name = "Eric", the User model has no way of knowing this is a model attribute, and it won’t be included in any save() requests. ActiveResource gets around this by using Ruby’s method_missing, which isn’t an option for clients written in many languages.

After talking it over with my coworkers, we realized there was value in giving a REST client access to a model’s schema, much as ActiveRecord has database access to ascertain a model’s schema. So to I proposed that Rails introduce into their standard REST controllers the “new.xml” route, and a patch with it, which was accepted this week.

Jester supports this, but it is disabled by default, as it will incur an HTTP request when you may not expect it, and your code may work fine without it. It also currently only works synchronously. You can trigger it by passing an option to build in a second hash parameter.

>>> eric = User.build({}, {checkNew: true})
GET http://localhost:3000/users/new.xml

>>> eric._properties
["active", "email", "name"]

>>> eric = User.build({lasers: 1000}, {checkNew: true})
GET http://localhost:3000/users/new.xml

>>> eric._properties
["active", "email", "name", "lasers"]
Dates are now parsed into actual JavaScript Date objects when a model is loaded from XML, thanks to code contributed by Nicholas Barthelemy. (SVN)

>>> post = Post.find(1)
GET http://localhost:3000/posts/1.xml
>>> post.created_at
Sat Mar 31 2007 03:01:56 GMT-0500 (Eastern Standard Time)
If a create or update request results in an XML response body, the model will reload itself using this XML. So, if your app changes an object on create or on update, this will be reflected in the client, as long as your controller renders the object in XML after saving it. Props to Nicholas Barthelemy for pointing out this was important before DHH suddenly committed changes in ActiveResource to do the exact same thing.

Client:

>>> eric = User.create({email: "emill@thoughtbot.com", name: "Eric Mill"})
POST http://localhost:3000/users.xml

>>> eric.unique_key
"frederick"
Controller:


def create
@user = User.new(params[:user])
@user.unique_key = "frederick"

respond_to do |format|
if @user.save
headers["Location"] = user_url(@user)
format.xml { render :xml => @user.to_xml, :status => 201}
end
end

You no longer need to include ObjTree.js in your own HTML. I took the parts of ObjTree I used, packed them using Dean Edwards’ packer script, and appended them to jester.js. In the same vein, I removed prototype.js from the repository, and replaced it with a smaller form of prototype, prototype.jester.js. Use this if you aren’t using Prototype for anything besides Jester, and want quicker loading times.

There were also some little fixes. If a resource is found by its ID, the ID will definitely be set in the object’s properties, even if the returned XML didn’t include it. Also, the ID is set more correctly when parsed out of a Location header, though I doubt this issue affected anyone, as it still worked fine in practice in most cases. There was also a bug in ObjTree where empty attributes (i.e. ””) weren’t even counted.


There’s still some significant work left for Jester, and I’m sure even more great ideas will come out of you guys, and out of the ActiveResource team. My targets for the next release, in order of importance:

Allowing “prefix options”, so that you can specify comments at /users/:user_id/comments.xml, instead of /comments.xml. Options would be passed inside the same hash as the query parameters, and separated by Jester. An example might look like:

// find all approved comments by user #1
>>> Comment.find("all", {user_id: 1, approved: true})
GET http://localhost:3000/users/1/comments.xml?approved=true
Calls to new.xml will only happen once per model, when the model is first declared, not on every call to build.
Support for “custom methods”, as specified in ActiveResource here
Support for manually specified URLs, as specified in ActiveResource here
Provide an option for a model so that Jester will submit POST and PUT request parameters as an XML body, instead of as form parameters.

Sunday, February 25, 2007

Good Coding Style Guidelines





















Aspect of JavaScript: Recommendation
Variable identifiers
Use camel-back capitalization and descriptive names that give an indication of what value the variable might be expected to hold. Appropriate variable names are most often made up of one or more nouns.
Function identifiers
Use the camel-back capitalization and descriptive names that indicate what operation they carry out. Appropriate function names are most often made up of one or more verbs.
Variable declarations
Avoid implicitly declared variables as they clutter the global namespace and lead to confusion. Always use var to declare your variables in the most specific scope possible. Avoid global variables whenever possible.
Functions
Pass values that need to be modified by reference by wrapping them in a composite type. Or, alternatively, return the new value that the variable should take on. Avoid changing global variables from inside functions. Declare functions in the document <head> or in a linked .js library.
Constructors
Indicate that object constructors are such by capitalizing the first letter of their identifier.
Comments
Use comments liberally. Complex conditionals should always be commented and so should functions.
Indentation
Indent each block two to five spaces further than the enclosing block. Doing so gives visual cues as to nesting depth and the relationship between constructs like if/else.
Modularization
Whenever possible, break your scripts up into externally linked libraries. Doing so facilitates code reuse and eases maintenance tasks.
Semicolons
Use them. Do not rely on implicit semicolon insertion.