diff --git a/FILES b/FILES index c3fb94e7..52e7a2d4 100644 --- a/FILES +++ b/FILES @@ -75,6 +75,8 @@ Haverbeke Intel src/js/codeview-mode.js APLV2 N/A Arnaud Renevier lib/zipfile.js Modified BSD[17] +Arnaud +Renevier lib/deflate.js MIT/X11 [21] Unreleased/Unpackaged Files (build/debug/development) ===================================================== @@ -114,3 +116,4 @@ Notes: [18] http://www.bramstein.com/projects/jlayout/ [19] http://www.bramstein.com/projects/jsizes/ [20] http://code.google.com/chrome/extensions/crx.html +[21] https://github.com/arenevier/zipfile/blob/master/external/deflate.js diff --git a/README b/README index ecca4ca5..9bd0f604 100644 --- a/README +++ b/README @@ -60,6 +60,7 @@ src/css/web-ui-fw-theme.css MIT Intel src/css/web-ui-fw-widgets.css MIT Intel lib/jszip.js MIT or GPLv3 Stuart Knightley[5] lib/zipfile.js Modified BSD Arnaud Renervier[6] +lib/deflate.js MIT/X11 Arnaud Renervier[6] lib/CodeMirror-2.21/LICENSE BSD-like Marijn Haverbeke lib/CodeMirror-2.21/lib/codemirror.css BSD-like Marijn Haverbeke lib/CodeMirror-2.21/lib/codemirror.js BSD-like Marijn Haverbeke diff --git a/index.html b/index.html index 16382dc8..84ad2ad8 100644 --- a/index.html +++ b/index.html @@ -25,6 +25,7 @@ + @@ -34,7 +35,6 @@ - @@ -52,6 +52,7 @@ + diff --git a/lib/deflate.js b/lib/deflate.js new file mode 100644 index 00000000..a8ec8800 --- /dev/null +++ b/lib/deflate.js @@ -0,0 +1,616 @@ + +/* +Copyright (c) 2008 notmasteryet + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* generic readers */ + +var base64alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + +/* RFC 4648 */ +function Base64Reader(base64) +{ + this.position = 0; + this.base64 = base64; + this.bits = 0; + this.bitsLength = 0; + this.readByte = function() { + if(this.bitsLength == 0) + { + var tailBits = 0; + while(this.position < this.base64.length && this.bitsLength < 24) + { + var ch = this.base64.charAt(this.position); + ++this.position; + if(ch > " ") + { + var index = base64alphabet.indexOf(ch); + if(index < 0) throw "Invalid character"; + if(index < 64) + { + if(tailBits > 0) throw "Invalid encoding (padding)"; + this.bits = (this.bits << 6) | index; + } + else + { + if(this.bitsLenght < 8) throw "Invalid encoding (extra)"; + this.bits <<= 6; + tailBits += 6; + } + this.bitsLength += 6; + } + } + + if(this.position >= this.base64.length) + { + if(this.bitsLength == 0) + return -1; + else if(this.bitsLength < 24) + throw "Invalid encoding (end)"; + } + + if(tailBits == 6) + tailBits = 8; + else if(tailBits == 12) + tailBits = 16; + this.bits = this.bits >> tailBits; + this.bitsLength -= tailBits; + } + + this.bitsLength -= 8; + var code = (this.bits >> this.bitsLength) & 0xFF; + return code; + }; +} + +function BitReader(reader) +{ + this.bitsLength = 0; + this.bits = 0; + this.reader = reader; + this.readBit = function() { + if(this.bitsLength == 0) { + var nextByte = this.reader.readByte(); + if(nextByte < 0) throw new "Unexpected end of stream"; + this.bits = nextByte; + this.bitsLength = 8; + } + + var bit = (this.bits & 1) != 0; + this.bits >>= 1; + --this.bitsLength; + return bit; + }; + this.align = function() { this.bitsLength = 0; } + this.readLSB = function(length) { + var data = 0; + for(var i=0;i 0) + return this.unreads.pop(); + else + return translator.readChar(); + }; + this.unreadChar = function(ch) { + this.unreads.push(ch); + }; + this.readToEnd = function() { + var slarge = ""; + var s = ""; + var ch = this.readChar(); + while(ch != null) + { + s += ch; + if(s.length > 1000) + { + slarge += s; + s = ""; + } + ch = this.readChar(); + } + return slarge + s; + }; + this.readLine = function() { + var s = ""; + var ch = this.readChar(); + if(ch == null) return null; + + while(ch != "\r" && ch != "\n") + { + s += ch; + ch = this.readChar(); + if(ch == null) return s; + } + if(ch == "\r") + { + ch = this.readChar(); + if(ch != null && ch != "\n") + this.unreadChar(ch); + } + return s; + }; +} + +function DefaultTranslator(reader) { + this.reader = reader; + this.readChar = function() { + var code = reader.readByte(); + return code < 0 ? null : String.fromCharCode(code); + }; +} + +/* RFC 2781 */ +function UnicodeTranslator(reader) { + this.reader = reader; + this.bomState = 0; + this.readChar = function() { + var b1 = reader.readByte(); + if(b1 < 0) return null; + var b2 = reader.readByte(); + if(b2 < 0) throw "Incomplete unicode character"; + if(this.bomState == 0 && b1 + b2 == 509) + { + this.bomState = b2 == 254 ? 1 : 2; + + b1 = reader.readByte(); + if(b1 < 0) return null; + b2 = reader.readByte(); + if(b2 < 0) throw "Incomplete unicode character"; + } + else + this.bomState = 1; + + var code = this.bomState == 1 ? (b2 << 8 | b1) : (b1 << 8 | b2); + return String.fromCharCode(code); + }; +} + +/* RFC 2279 */ +function Utf8Translator(reader) { + this.reader = reader; + this.waitBom = true; + this.pendingChar = null; + this.readChar = function() { + var ch = null; + do + { + if(this.pendingChar != null) + { + ch = this.pendingChar; + this.pendingChar = null; + } + else + { + var b1 = this.reader.readByte(); + if(b1 < 0) return null; + + if((b1 & 0x80) == 0) + { + ch = String.fromCharCode(b1); + } + else + { + var currentPrefix = 0xC0; + var validBits = 5; + do + { + var mask = currentPrefix >> 1 | 0x80; + if((b1 & mask) == currentPrefix) break; + currentPrefix = currentPrefix >> 1 | 0x80; + --validBits; + } while(validBits >= 0); + if(validBits > 0) + { + var code = (b1 & ((1 << validBits) - 1)); + for(var i=5;i>=validBits;--i) + { + var bi = this.reader.readByte(); + if((bi & 0xC0) != 0x80) throw "Invalid sequence character"; + code = (code << 6) | (bi & 0x3F); + } + if(code <= 0xFFFF) + { + if(code == 0xFEFF && this.waitBom) + ch = null; + else + ch = String.fromCharCode(code); + } + else + { + var v = code - 0x10000; + var w1 = 0xD800 | ((v >> 10) & 0x3FF); + var w2 = 0xDC00 | (v & 0x3FF); + this.pendingChar = String.fromCharCode(w2); + ch = String.fromCharCode(w1); + } + } + else + throw "Invalid character"; + } + } + this.waitBom = false; + } while(ch == null); + return ch; + }; +} + +/* inflate stuff - RFC 1950 */ + +var staticCodes, staticDistances; + +var encodedLengthStart = new Array(3,4,5,6,7,8,9,10, + 11,13,15,17,19,23,27,31,35,43,51,59,67,83,99, + 115,131,163,195,227,258); + +var encodedLengthAdditionalBits = new Array(0,0,0,0,0,0,0,0, + 1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0); + +var encodedDistanceStart = new Array(1,2,3,4, 5,7,9, + 13,17,25, 33,49,65, 97,129,193,257,385,513,769,1025,1537,2049, + 3073,4097,6145,8193,12289,16385,24577); + +var encodedDistanceAdditionalBits = new Array(0,0,0,0,1,1,2,2,3,3,4,4, + 5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13); + +var clenMap = new Array(16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15); + +function buildCodes(lengths) +{ + var codes = new Array(lengths.length); + var maxBits = lengths[0]; + for (var i=1; i 0) + { + var code = new Object(); + code.bits = codes[i]; + code.length = lengths[i]; + code.index = i; + nonEmptyCodes.push(code); + } + } + return buildTreeBranch(nonEmptyCodes, 0, 0); +} + +function buildTreeBranch(codes, prefix, prefixLength) +{ + if(codes.length == 0) return null; + + var zeros = new Array(0); + var ones = new Array(0); + var branch = new Object(); + branch.isLeaf = false; + for(var i=0; i> (codes[i].length - prefixLength - 1)) & 1) > 0; + if(nextBit) + { + ones.push(codes[i]); + } + else + { + zeros.push(codes[i]); + } + } + } + if(!branch.isLeaf) + { + branch.zero = buildTreeBranch(zeros, (prefix << 1), prefixLength + 1); + branch.one = buildTreeBranch(ones, (prefix << 1) | 1, prefixLength + 1); + } + return branch; +} + +function readDynamicTrees(bitReader) +{ + var hlit = bitReader.readLSB(5) + 257; + var hdist = bitReader.readLSB(5) + 1; + var hclen = bitReader.readLSB(4) + 4; + + var clen = new Array(19); + for(var i=0; i= this.buffer.length) + { + var item = this.decodeItem(); + if(item == null) return -1; + switch(item.itemType) + { + case 0: + this.buffer = this.buffer.concat(item.array); + break; + case 2: + this.buffer.push(item.symbol); + break; + case 3: + var j = this.buffer.length - item.distance; + for(var i=0;i 0xC000) + { + var shift = this.buffer.length - 0x8000; + if(shift > this.bufferPosition) shift = this.bufferPosition; + this.buffer.splice(0, shift); + this.bufferPosition -= shift; + } + return symbol; + } + + this.decodeItem = function() { + if(this.state == 2) return null; + + var item; + if(this.state == 0) + { + this.blockFinal = this.bitReader.readBit(); + var blockType = this.bitReader.readLSB(2); + switch(blockType) + { + case 0: + this.bitReader.align(); + var len = this.bitReader.readLSB(16); + var nlen = this.bitReader.readLSB(16); + if((len & ~nlen) != len) throw "Invalid block type 0 length"; + + item = new Object(); + item.itemType = 0; + item.array = new Array(len); + for(var i=0;i 256) + { + var lengthCode = p.index; + if(lengthCode > 285) throw new "Invalid length code"; + + var length = encodedLengthStart[lengthCode - 257]; + if(encodedLengthAdditionalBits[lengthCode - 257] > 0) + { + length += this.bitReader.readLSB(encodedLengthAdditionalBits[lengthCode - 257]); + } + + p = this.distancesTree; + while(!p.isLeaf) + { + p = this.bitReader.readBit() ? p.one : p.zero; + } + + var distanceCode = p.index; + var distance = encodedDistanceStart[distanceCode]; + if(encodedDistanceAdditionalBits[distanceCode] > 0) + { + distance += this.bitReader.readLSB(encodedDistanceAdditionalBits[distanceCode]); + } + + item.itemType = 3; + item.distance = distance; + item.length = length; + } + else + { + item.itemType = 1; + this.state = this.blockFinal ? 2 : 0; + } + return item; + }; +} + +/* initialization */ +initializeStaticTrees(); diff --git a/src/assets/groups.json b/src/assets/groups.json index bb05bd01..202514fa 100644 --- a/src/assets/groups.json +++ b/src/assets/groups.json @@ -21,8 +21,25 @@ "Boolean": ["#0._hidden_node.jqm_input_boolean"] } ], - "List Views": [ - "List", "OrderedList", "ListItem", "ListDivider", "ListButton" + "List Views": [ { + "Single Lists": [ + "SimpleList", + "ButtonList", + "TextList", + "IconList", + "ThumbnailList", + "SimpleListItem", + "ListDivider" + ], + "Split Lists": [ + "ButtonSplitList", + "TextSplitList", + "IconSplitList", + "ThumbnailSplitList", + "SimpleListItem", + "ListDivider" + ] + } ], "Image": ["Image"] } diff --git a/src/css/builder.css b/src/css/builder.css index d5c76ff7..fc3e8a96 100644 --- a/src/css/builder.css +++ b/src/css/builder.css @@ -927,6 +927,14 @@ div.propertyItems label[for] { div.propertyItems label[for|=id] { text-transform: uppercase; } +div.propertyItems .delete.button { + text-indent: -3000px; + margin-top: 4px; + height: 25px; + width: 25px; + background-repeat: no-repeat; + background-position: left top; +} /* Widget View widget styles @@ -1411,7 +1419,8 @@ div.propertyItems label[for|=id] { bottom: 33px; background-image: url("images/cloneButton_up.png"); } -.projectBox .button:hover, .projectBox .button:focus { +.projectBox .button:hover, .projectBox .button:focus, +div.propertyItems .button:hover, div.propertyItems .button:focus { opacity: 0.8; } @@ -1560,3 +1569,118 @@ input.screenCoordinate::-webkit-inner-spin-button { #exportDialog .buttonStyle { margin: 0 0.6em; } + +#eventHandlerDialog { + overflow: hidden; +} + +#eventHandlerDialog .title { + position: relative; + height: 52px; + background-color: #E4E5DF; +} + +#eventHandlerDialog .title > label { + position: absolute; + left: 10px; + top: 16px; + font-size : 13px; + color: #4d4d4d; + font-family: OpenSans-SB; + font-weight: 600; /* semi-bold */ +} + + +#eventHandlerDialog .wrap_left { + position: relative; + width: 230px; + border-right: 1px solid #dededc; +} + +#eventHandlerDialog .wrap_left .container { + position: absolute; + width: 100%; + margin: 10px; +} + + +#eventHandlerDialog .wrap_left .container * { + display: inline-block; +} + +#eventHandlerDialog .wrap_left .container select { + position: absolute; + top: 10px; + left: 0px; + width: 270px; +} + +#eventHandlerDialog .wrap_left .container fieldset { + position: absolute; + top: 40px; + width: 244px; + height: 370px; + overflow: auto; +} + +#eventHandlerDialog .wrap_left .container fieldset ul { + margin: 0px; +} + +#eventHandlerDialog .wrap_left .container fieldset li { + display: block; + padding: 8px 8px 8px 8px; + margin-left: -36px; + width: 200px; + height: 34px; + border: 8px transparent; + border-bottom: 1px solid #CCC; +} + +#eventHandlerDialog .wrap_left .container fieldset li.ui-selected { + background-color: #4AE57B; +} + +#eventHandlerDialog .wrap_left .container fieldset li > a.link { + padding-top: 8px; + width: 160px; + height: 34px; +} + +#eventHandlerDialog .wrap_left .container fieldset a.ui-button { + margin-top: 0px; + float: right; +} + +#eventHandlerDialog .wrap_left .container button.doneButton { + position: absolute; + top: 440px; + left: 46px; + width: 180px; +} + +#eventHandlerDialog .wrap_right { + width: 630px; +} + +#eventHandlerDialog .wrap_right .container { + overflow: auto; + resize: none; + height: 600px; + width: 98%; + margin: 6px; +} + +#eventHandlerDialog .wrap_right .container .CodeMirror { + margin: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + -moz-box-shadow: inset 0 0 4px #000000; + -webkit-box-shadow: inset 0 0 4px #000000; + box-shadow: inset 0 0 4px #000000; + height: 470px; +} + +#eventHandlerDialog .wrap_right .container .CodeMirror .CodeMirror-scroll { + height: 464px; +} diff --git a/src/css/composer.css b/src/css/composer.css index 5663474c..40b1d136 100644 --- a/src/css/composer.css +++ b/src/css/composer.css @@ -48,7 +48,7 @@ /*************************/ /* Class specific tweaks */ /*************************/ -.nrc-sortable-container.ui-content .adm-node { +.nrc-sortable-container.ui-content > * { margin-top: 10px; margin-bottom: 10px; } diff --git a/src/css/images/widgets/jqm_button_list.svg b/src/css/images/widgets/jqm_button_list.svg new file mode 100644 index 00000000..647be337 --- /dev/null +++ b/src/css/images/widgets/jqm_button_list.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +Button List + + + + + + + + + diff --git a/src/css/images/widgets/jqm_button_split_list.svg b/src/css/images/widgets/jqm_button_split_list.svg new file mode 100644 index 00000000..edd6c5ce Binary files /dev/null and b/src/css/images/widgets/jqm_button_split_list.svg differ diff --git a/src/css/images/widgets/jqm_icon_list.svg b/src/css/images/widgets/jqm_icon_list.svg new file mode 100644 index 00000000..af655fe4 Binary files /dev/null and b/src/css/images/widgets/jqm_icon_list.svg differ diff --git a/src/css/images/widgets/jqm_icon_list_button.svg b/src/css/images/widgets/jqm_icon_list_button.svg new file mode 100644 index 00000000..53c6e1f2 --- /dev/null +++ b/src/css/images/widgets/jqm_icon_list_button.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + +Icon List Button + + + diff --git a/src/css/images/widgets/jqm_icon_split_list.svg b/src/css/images/widgets/jqm_icon_split_list.svg new file mode 100644 index 00000000..7293b6df Binary files /dev/null and b/src/css/images/widgets/jqm_icon_split_list.svg differ diff --git a/src/css/images/widgets/jqm_list.svg b/src/css/images/widgets/jqm_list.svg index ff9dd70b..4481b865 100644 --- a/src/css/images/widgets/jqm_list.svg +++ b/src/css/images/widgets/jqm_list.svg @@ -32,7 +32,7 @@ -Unordered List +Simple List diff --git a/src/css/images/widgets/jqm_list_split_button.svg b/src/css/images/widgets/jqm_list_split_button.svg deleted file mode 100644 index 2411051f..00000000 --- a/src/css/images/widgets/jqm_list_split_button.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - -List Split Button - - - - diff --git a/src/css/images/widgets/jqm_text_list.svg b/src/css/images/widgets/jqm_text_list.svg new file mode 100644 index 00000000..a093acef --- /dev/null +++ b/src/css/images/widgets/jqm_text_list.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +Text List + + + + + + + + + diff --git a/src/css/images/widgets/jqm_text_list_button.svg b/src/css/images/widgets/jqm_text_list_button.svg new file mode 100644 index 00000000..7bee4b5c Binary files /dev/null and b/src/css/images/widgets/jqm_text_list_button.svg differ diff --git a/src/css/images/widgets/jqm_text_split_list.svg b/src/css/images/widgets/jqm_text_split_list.svg new file mode 100644 index 00000000..f9faa178 Binary files /dev/null and b/src/css/images/widgets/jqm_text_split_list.svg differ diff --git a/src/css/images/widgets/jqm_thumbnail_list.svg b/src/css/images/widgets/jqm_thumbnail_list.svg new file mode 100644 index 00000000..0538fc87 Binary files /dev/null and b/src/css/images/widgets/jqm_thumbnail_list.svg differ diff --git a/src/css/images/widgets/jqm_thumbnail_list_button.svg b/src/css/images/widgets/jqm_thumbnail_list_button.svg new file mode 100644 index 00000000..2dfb18aa --- /dev/null +++ b/src/css/images/widgets/jqm_thumbnail_list_button.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + +Thumbnail List Button + + + diff --git a/src/css/images/widgets/jqm_thumbnail_split_list.svg b/src/css/images/widgets/jqm_thumbnail_split_list.svg new file mode 100644 index 00000000..309ee419 Binary files /dev/null and b/src/css/images/widgets/jqm_thumbnail_split_list.svg differ diff --git a/src/js/adm.js b/src/js/adm.js index 364f82fa..c83a5333 100644 --- a/src/js/adm.js +++ b/src/js/adm.js @@ -518,7 +518,7 @@ ADM.endTransaction = function () { * @return {ADMNode} The child object, on success; null, on failure. */ ADM.addChild = function (parentRef, childRef, dryrun) { - var parent, child; + var parent, child, oldParent, oldType, oldZone, oldZoneIndex; parent = ADM.toNode(parentRef); if (!parent) { @@ -538,6 +538,12 @@ ADM.addChild = function (parentRef, childRef, dryrun) { return null; } + oldParent = child.getParent(); + if (oldParent) { + oldType = child.getType(); + oldZone = child.getZone(); + oldZoneIndex = child.getZoneIndex(); + } if (parent.addChild(child, dryrun)) { if (dryrun) { return true; @@ -547,7 +553,11 @@ ADM.addChild = function (parentRef, childRef, dryrun) { ADM.transaction({ type: "add", parent: child.getParent(), - child: child + child: child, + oldParent: oldParent, + oldType: oldType, + oldZone: oldZone, + oldZoneIndex: oldZoneIndex }); return child; } @@ -634,7 +644,7 @@ ADM.addChildRecursive = function (parentRef, childRef, dryrun) { * @private */ ADM.insertChildRelative = function (siblingRef, childRef, offset, dryrun) { - var sibling, child; + var sibling, child, oldParent, oldType, oldZone, oldZoneIndex; sibling = ADM.toNode(siblingRef); if (!sibling) { @@ -655,6 +665,12 @@ ADM.insertChildRelative = function (siblingRef, childRef, offset, dryrun) { childRef); } + oldParent = child.getParent(); + if (oldParent) { + oldType = child.getType(); + oldZone = child.getZone(); + oldZoneIndex = child.getZoneIndex(); + } if (sibling.insertChildRelative(child, offset, dryrun)) { if (dryrun) { return true; @@ -663,7 +679,11 @@ ADM.insertChildRelative = function (siblingRef, childRef, offset, dryrun) { type: "insertRelative", sibling: sibling, child: child, - offset: offset + offset: offset, + oldParent: oldParent, + oldType: oldType, + oldZone: oldZone, + oldZoneIndex: oldZoneIndex }); return child; } @@ -919,21 +939,21 @@ ADM.transaction = function (obj) { */ ADM.undo = function () { var obj, undo = function (obj) { - if (obj.type === "add") { + if ( ["add", "insertRelative", "move"].indexOf(obj.type) !== -1) { ADM.ensurePageInactive(obj.child); - obj.parent.removeChild(obj.child); + if (obj.oldParent) { + if (obj.oldType !== obj.child.getType()) + obj.child.morphTo(obj.oldType); + obj.child.moveNode(obj.oldParent, obj.oldZone, obj.oldZoneIndex); + ADM.setSelected(obj.child); + } + else + obj.child.getParent().removeChild(obj.child); } else if (obj.type === "remove") { obj.parent.insertChildInZone(obj.child, obj.zone, obj.zoneIndex); ADM.setSelected(obj.child); } - else if (obj.type === "move") { - obj.node.moveNode(obj.oldParent, obj.oldZone, obj.oldZoneIndex); - ADM.setSelected(obj.node); - } - else if (obj.type === "insertRelative") { - obj.sibling.getParent().removeChild(obj.child); - } else if (obj.type === "propertyChange") { // TODO: this could require deeper copy of complex properties obj.node.setProperty(obj.property, obj.oldValue, obj.data); @@ -1167,18 +1187,8 @@ function ADMNode(widgetType) { var currentType = widgetType, widget, zones, length, i, func; this._valid = false; - this._inheritance = []; - - while (currentType) { - widget = BWidgetRegistry[currentType]; - if (typeof widget === "object") { - this._inheritance.push(currentType); - currentType = widget.parent; - } else { - console.error("Error: invalid type hierarchy creating ADM node"); - return; - } - } + this._inheritance = [widgetType]; + $.merge(this._inheritance, BWidget.getAncestors(widgetType)); this._uid = ++ADMNode.prototype._lastUid; @@ -1487,7 +1497,7 @@ ADMNode.prototype.findNodesByProperty = function (propertyFilter) { // recurse on children children = this.getChildren(); - for (i = children.length - 1; i >= 0; i--) { + for (i = 0; i < children.length; i++) { result = result.concat(children[i].findNodesByProperty(propertyFilter)); } return result; @@ -1574,6 +1584,18 @@ ADMNode.prototype.getChildrenCount = function () { return count; }; +/** + * Tests whether this node has has event handlers. + * + * @return {Boolean} True if the node has at least one event handler. + */ + +ADMNode.prototype.hasEventHandlers = function() { + return !$.isEmptyObject(this.getMatchingProperties( + {type: 'event', value: /.+/} + )); +} + /** * Tests whether this node has user-visible descendants that will be displayed * in the outline view. @@ -1599,6 +1621,18 @@ ADMNode.prototype.hasUserVisibleDescendants = function () { return false; }; +/** + * Change this node to another type + * + * @param {String} type The type this node will morph to. + * @return {ADMNode} The morphed node. + */ +ADMNode.prototype.morphTo = function (type) { + var morphedChild = ADM.createNode(type); + this._inheritance = morphedChild._inheritance; + this._zones = morphedChild._zones; + return this; +}; /** * Adds given child object to this object, generally at the end of the first * zone that accepts the child. @@ -1666,11 +1700,19 @@ ADMNode.prototype.addChild = function (child, dryrun) { ADMNode.prototype.addChildToZone = function (child, zoneName, zoneIndex, dryrun) { // requires: assumes cardinality is "N", or a numeric string - var add = false, myType, childType, zone, cardinality, limit; + var add = false, myType, childType, zone, cardinality, limit, morph, + morphedChildType, morphedChild; myType = this.getType(); childType = child.getType(); zone = this._zones[zoneName]; + morph = BWidget.getZone(myType, zoneName).morph; + if (morph) { + morphedChildType = morph(childType, myType); + if (morphedChildType !== childType) { + childType = morphedChildType; + } + } if (!BWidget.zoneAllowsChild(myType, zoneName, childType)) { if (!dryrun) { console.warn("Warning: zone " + zoneName + @@ -1693,6 +1735,7 @@ ADMNode.prototype.addChildToZone = function (child, zoneName, zoneIndex, return false; } + cardinality = cardinality.max || cardinality; if (cardinality !== "N") { limit = parseInt(cardinality, 10); if (zone.length >= limit) { @@ -1777,7 +1820,8 @@ ADMNode.prototype.insertChildInZone = function (child, zoneName, index, } } - var zone = this._zones[zoneName]; + var zone = this._zones[zoneName], oldParent, + myType, childType, morph, morphedChildType; if (!zone) { console.error("Error: zone not found in insertChildInZone: " + zoneName); @@ -1788,7 +1832,22 @@ ADMNode.prototype.insertChildInZone = function (child, zoneName, index, return false; } if (child instanceof ADMNode) { + oldParent = child.getParent(); + if (oldParent) { + if (oldParent === this && child.getZone() === zoneName + && child.getZoneIndex() < index) + index --; + return child.moveNode(this, zoneName, index, dryrun); + } if (!dryrun) { + myType = this.getType(); + childType = child.getType(); + morph = BWidget.getZone(myType, zoneName).morph; + if (morph) { + morphedChildType = morph(childType, myType); + if (morphedChildType != childType) + child.morphTo(morphedChildType); + } zone.splice(index, 0, child); setRootRecursive(child, this._root); @@ -1904,12 +1963,30 @@ ADMNode.prototype.removeChild = function (child, dryrun) { * @return {ADMNode} The removed child, or null if not found. */ ADMNode.prototype.removeChildFromZone = function (zoneName, index, dryrun) { - var zone, removed, child, parentNode, parent; + var zone, removed, child, parent, cardinality, min; zone = this._zones[zoneName]; if (!zone) { console.error("Error: no such zone found while removing child: " + zoneName); } + cardinality = BWidget.getZoneCardinality(this.getType(), zoneName); + if (!cardinality) { + console.warn("Warning: no cardinality found for zone " + zoneName); + return false; + } + + if (cardinality.min) { + min = parseInt(cardinality.min, 10); + + if (zone.length <= min) { + alert("At least " + + cardinality.min + " " + + BWidget.getDisplayLabel(zone[index].getType()) + + (min === 1 ? " " : "s ") + + (min === 1 ? "is": "are") + " required and cannot be deleted!"); + return false; + } + } if (dryrun) { removed = [zone[index]]; @@ -1960,11 +2037,15 @@ ADMNode.prototype.foreach = function (func) { * @param {String} The name of the property. * @return {String} The generated property value. */ -ADMNode.prototype.generateUniqueProperty = function (property) { +ADMNode.prototype.generateUniqueProperty = function (property, force) { var generate, design, myType, length, i, genLength, max, num, existing = []; myType = this.getType(); generate = BWidget.getPropertyAutoGenerate(myType, property); - if (!generate) { + // If force argument is set, then set the generate as myType and continue + // to run. + if (!generate && force) { + generate = myType.toLowerCase(); + } else if (!generate) { return undefined; } @@ -2230,8 +2311,9 @@ ADMNode.prototype.isPropertyExplicit = function (property) { * relevant info for performing an undo of this operation. */ ADMNode.prototype.setProperty = function (property, value, data, raw) { - var orig, func, changed, type, rval = { }, defaultValue; - type = BWidget.getPropertyType(this.getType(), property); + var orig, func, changed, rval = { }, defaultValue, + type = BWidget.getPropertyType(this.getType(), property); + if (!type) { console.error("Error: attempted to set non-existent property: " + property); @@ -2249,14 +2331,15 @@ ADMNode.prototype.setProperty = function (property, value, data, raw) { } } - // TODO: In HTML5 the rules for ids are not this strict, so this should be - // corrected with reference to the spec. - // HTML id naming rules: - // Must begin with a letter A-Z or a-z + // TODO: In HTML5 the rules for ids are not this strict, but JQM and jQuery don't work well + // with special chars, such as "$" "%" "/" accepted by HTML5. + // + // So we use the following id naming rules: + // Must begin with a letter A-Z or a-z or 0-9 // Can be followed by: letters (A-Za-z), digits (0-9), hyphens ("-"), and underscores ("_") // In HTML, all values are case-insensitive if (property == "id") { - var pattern = /^[a-zA-Z]([\w-]*)$/; + var pattern = /^[a-zA-Z0-9]([\w-]*)$/; if (value && !pattern.test(value)) { console.error("Error: attempted to set invalid id"); return rval; @@ -2300,6 +2383,14 @@ ADMNode.prototype.setProperty = function (property, value, data, raw) { { type: "propertyChanged", node: this, property: property, oldValue: orig, newValue: value }); + + // Event handler saving after ID/event property changed. + if (property === 'id') { + if (this.hasEventHandlers()) + $.rib.saveEventHandlers(); + } else if (type === 'event') { + $.rib.saveEventHandlers(); + } rval.result = true; } return rval; diff --git a/src/js/composer.js b/src/js/composer.js index 7f85a8c7..2e82e5b6 100644 --- a/src/js/composer.js +++ b/src/js/composer.js @@ -279,58 +279,6 @@ $(function() { } }; - var addNewNodeOnDrop = function (domParent, domChildren, adm, pid, - nodeRef, role, domNode) { - var newNode, domSibling, sid, admChildren, domIndex, debug; - - debug = (window.top.$.rib && window.top.$.rib.debug()); - domIndex = domChildren.index(domNode); - - // Append first(only)/last child to this container - if (domIndex >= domChildren.length-1 || role === 'page') { - if (adm.addChild(pid, nodeRef, true)) { - newNode = adm.addChild(pid, nodeRef); - debug && console.log('Appended node',role); - newNode && adm.setSelected(newNode); - } else { - console.warn('Append child failed:',role); - } - } else if (domIndex > 0) { - // Insert nth child into this container - domSibling = $(domNode, domParent).prev('.adm-node'); - sid = domSibling.attr('data-uid'); - if (adm.insertChildAfter(sid, nodeRef, true)) { - newNode = adm.insertChildAfter(sid, nodeRef); - debug && console.log('Inserted nth node',role); - newNode && adm.setSelected(newNode); - } else { - console.warn('Insert nth child failed:',role); - } - } else { - // Add 1st child into an empty container - if (domChildren.length-1 <= 0) { - if (adm.addChild(pid, nodeRef, true)) { - newNode = adm.addChild(pid, nodeRef); - debug && console.log('Added 1st node',role); - newNode && adm.setSelected(newNode); - } else { - console.warn('Add 1st child failed:',role); - } - } else { - // Insert 1st child into non-empty container - admChildren = adm.toNode(pid).getChildren(); - sid = admChildren.length && admChildren[0].getUid(); - if (adm.insertChildBefore(sid, nodeRef, true)) { - newNode = adm.insertChildBefore(sid, nodeRef); - debug && console.log('Inserted 1st node', role); - newNode && adm.setSelected(newNode); - } else { - console.warn('Insert 1st child failed:', role); - } - } - } - }; - window.top.$.rib = window.top.$.rib || {}; window.top.$.rib.dndfilter = dndfilter; @@ -377,7 +325,7 @@ $(function() { } }; $(e.target).subtree().add(document) - .unbind('click vmousedown vmousecancel vmouseup vmouseover focus' + .unbind('click vmousedown vmousecancel vmouseup vmouseover focus focusin' + ' vmouseout blur mousedown touchmove'); $(e.target).subtree('.adm-node:not(.delegation),.orig-adm-node').each( @@ -550,6 +498,7 @@ $(function() { '> .ui-collapsible-content > .adm-node,' + '> ul > li.adm-node,' + '> div > .adm-node,' + + '> div > div > a > .adm-node,' + '> *.orig-adm-node:not(.ui-header,.ui-content,.ui-footer)', start: function(event, ui){ trackOffsets('start: ',ui,$(this).data('sortable')); @@ -634,16 +583,14 @@ $(function() { }, stop: function(event, ui){ trackOffsets('stop: ',ui,$(this).data('sortable')); - var type, isDrop, + var isDrop, pid = $(this).attr('data-uid'), - node = null, adm = window.parent.ADM, - bw = window.parent.BWidget, root = adm.getDesignRoot(), - node, zones, newParent, newZone, - rdx, idx, cid, pid, sid, + nodeRef, newParent, newNode, + cid, pid, sibling, children, parent, - role, card; + role, prevItem = ui.item, nextItem = ui.item; role = $(this).attr('data-role') || ''; @@ -694,87 +641,50 @@ $(function() { ui.item.remove(); return false; } - - // Drop from palette: add a node if (isDrop) { if (ui.item.data('adm-node')) { - type = ui.item.data('adm-node').type; + nodeRef = ui.item.data('adm-node').type; } - if (!type) { + if (!nodeRef) { console.warn('Drop failed: Missing node type'); ui.item.remove(); return false; } - - children = $($(this).sortable('option', 'items'), this) - .add(ui.item); - addNewNodeOnDrop(this, children, adm, pid, type, - role, ui.item); - ui.item.remove(); - return; - - // Sorted from layoutView: move a node - } else { - children = ui.item.parent().children('.adm-node') - .add(ui.item); - idx = children.index(ui.item); + } + else { cid = ui.item.attr('data-uid'); // closest() will select current element if it matches // the selector, so we start with its parent. + nodeRef = cid && root.findNodeByUid(cid); + if (event && event.ctrlKey) { + nodeRef = adm.copySubtree(nodeRef); + } + } + + while (prevItem[0] || nextItem[0]) { + prevItem = prevItem.prev('.adm-node'); + if (prevItem[0] && (newNode = adm.insertChildAfter + (prevItem.attr('data-uid'), nodeRef))) + break; + nextItem = nextItem.next('.adm-node'); + if (nextItem[0] && (newNode = adm.insertChildBefore + (nextItem.attr('data-uid'), nodeRef))) + break; + } + if (!prevItem[0] && !nextItem[0]) { pid = ui.item.parent() .closest('.adm-node.ui-sortable,'+ '.orig-adm-node.ui-sortable') .attr('data-uid'); - node = cid && root.findNodeByUid(cid); - if (event && event.ctrlKey) { - node = adm.copySubtree(node); // Clone it - addNewNodeOnDrop(this, children, adm, pid, node, - role, ui.item); - } else { - newParent = pid && root.findNodeByUid(pid); - zones = newParent && bw.getZones(newParent.getType()); - card = newParent && zones && - bw.getZoneCardinality(newParent.getType(), - zones[0]); - - // Notify the ADM that element has been moved - if ((zones && zones.length===1 && card !== '1')) { - if (!node || - !adm.moveNode(node, newParent, zones[0], - idx)) { - console.warn('Move node failed'); - ui.item.remove(); - return false; - } else { - debug && console.log('Move node worked'); - if (node) adm.setSelected(node.getUid()); - } - } else if (node && newParent && - newParent.getType() === 'Header') { - for (var z=0; z < zones.length; z++) { - if (adm.moveNode(node, newParent, zones[z], - 0, true)) { - newZone = zones[z]; - break; - } - } - if (newZone) { - adm.moveNode(node, newParent, newZone, 0); - debug && console.log('Move node worked'); - if (node) adm.setSelected(node.getUid()); - } else { - console.warn('Move node failed'); - ui.item.remove(); - return false; - } - } else { - console.warn('Move node failed: invalid zone'); - ui.item.remove(); - return false; - } + newParent = pid && root.findNodeByUid(pid); + if (!(newNode = adm.addChild(newParent, nodeRef))) { + ui.item.remove(); + return true; } } + if (newNode) + adm.setSelected(newNode); } }) .bind('mousedown.composer', function(event) { @@ -803,6 +713,12 @@ $(function() { var inputs = targets.find('input'); $(inputs).disableSelection(); + setTimeout(function(){ + var focusElement = parent.window.focusElement; + if (focusElement) { + parent.window.$(focusElement).focus(); + } + }); }); function messageHandler(e) { diff --git a/src/js/fs.js b/src/js/fs.js index be20e225..190cf9e9 100644 --- a/src/js/fs.js +++ b/src/js/fs.js @@ -48,6 +48,10 @@ $(function () { mime: 'text/css', suffix: ['css'] }, + zip: { + mime: 'application/zip', + suffix: ['zip'] + }, any: { mime: '*', suffix: ['*'] @@ -551,6 +555,10 @@ $(function () { */ checkFileType: function (type, file) { var arrString, rule; + if (type === 'any') { + // if file type is any, we don't check + return true; + } arrString = fsUtils.fileTypes[type.toLowerCase()].suffix.join('|'); rule = new RegExp("\\.(" + arrString + ")$", "i"); // TODO: May need to read the "content-type" to check the type diff --git a/src/js/projects.js b/src/js/projects.js index fbb8a852..eb2065cc 100644 --- a/src/js/projects.js +++ b/src/js/projects.js @@ -27,7 +27,9 @@ $(function () { // Filter to find sandbox resources relativeFilter:{ type: "url-uploadable", - value: /^(?!(https?|ftp):\/+).+/i + value: function (valueObject) { + return valueObject.inSandbox; + } }, // Object to save refernce count for sandbox resource resourceRef: {}, @@ -57,7 +59,9 @@ $(function () { defaultValue: "Default" }, }, - themesList: {} + themesList: {}, + allThemes: [] + }; /* Asynchronous. init pmUtils. @@ -144,6 +148,14 @@ $(function () { }, async: false }); + var listThemeFiles = function (entries) { + // make allThemes array empty + pmUtils.allThemes.length = 0; + $.each(entries, function (index, e) { + pmUtils.allThemes.push(e.name); + }); + }; + $.rib.fsUtils.ls('/themes/', listThemeFiles, null); } /***************** APIs to manipulate projects *************************/ @@ -299,6 +311,16 @@ $(function () { */ pmUtils.setProperties = function (pid, properties) { var i, pInfo, temp, newThemeName, oldThemeName, props, p; + var getThemeFile = function (themeName) { + var theme; + if (jQuery.inArray(themeName + ".min.css", + $.rib.pmUtils.allThemes) !== -1) { + theme = '/themes/' + themeName + ".min.css"; + } else { + theme = '/themes/' + themeName + ".css"; + } + return theme; + }; // get the original object of pInfo pid && (pInfo = pmUtils._projectsInfo[pid]); if (!(pid && pInfo) || typeof properties !== "object") { @@ -313,12 +335,16 @@ $(function () { oldThemeName = pmUtils.getProperty(pid, 'theme'); //update css property in header if (oldThemeName === "Default") { - $.rib.addSandboxHeader('css', '/themes/' + newThemeName + '.css'); + newThemeName = getThemeFile(newThemeName); + $.rib.addSandboxHeader('css', newThemeName); } else if (newThemeName === "Default") { - $.rib.removeSandboxHeader('css', '/themes/' + oldThemeName + '.css'); + oldThemeName = getThemeFile(oldThemeName); + $.rib.removeSandboxHeader('css', oldThemeName); } else { - $.rib.removeSandboxHeader('css', '/themes/' + oldThemeName + '.css'); - $.rib.addSandboxHeader('css', '/themes/' + newThemeName + '.css'); + oldThemeName = getThemeFile(oldThemeName); + newThemeName = getThemeFile(newThemeName); + $.rib.removeSandboxHeader('css', oldThemeName); + $.rib.addSandboxHeader('css', newThemeName); } } // if the item has schema then check the type @@ -956,6 +982,7 @@ $(function () { * @return {None} */ pmUtils.addRefCount = function (refPath) { + refPath = refPath.value || refPath; if (!pmUtils.resourceRef[refPath]) { pmUtils.resourceRef[refPath] = 1; } else { @@ -972,6 +999,7 @@ $(function () { */ pmUtils.reduceRefCount = function (refPath) { var projectDir = pmUtils.ProjectDir + "/" + pmUtils.getActive() + "/"; + refPath = refPath.value || refPath; if (pmUtils.resourceRef.hasOwnProperty(refPath)) { pmUtils.resourceRef[refPath]--; // Delete the resource if the reference count is 0 @@ -1048,43 +1076,59 @@ $(function () { * upload a new theme to projects * * @param {String} theme file - * @return {Bool} return true if success, false when fails + * @param {Function} callback when upload file successfully */ - pmUtils.uploadTheme = function (themeFile) { - var themeName = themeFile.name.replace(/.css$/g, ""); - var parseSwatches = function (buffer) { - var swatches = [], lines = [], arr, i, - re = /^\.ui-bar-([a-z]) {$/i; - lines = buffer.split('\n'); - for (i = 0; i < lines.length; i++) { - arr =re.exec(lines[i]); - //if swatch is not found in swatcher list, add it into swatches - if (arr && jQuery.inArray(arr[1], swatches) === -1) { - swatches.push(arr[1]); - } + pmUtils.uploadTheme = function (themeFile, handler) { + var type, reader; + // get file type. Currently we get file type + // just accordint to suffix of file name + var getFileType = function (fileName) { + var type,re = /(?:.*)+\.(zip|css)$/i; + type = re.exec(fileName); + if (type) { + return type[1].toLowerCase(); + } else { + return "unsupported"; } - return swatches; }; - //write themeFile to sandbox - $.rib.fsUtils.write('/themes/' + themeFile.name, themeFile, function () { - //read file to buffer - $.rib.fsUtils.read('/themes/' + themeFile.name, function (result) { - try { - var swatches = parseSwatches(result); - if (swatches.length) { - pmUtils.themesList[themeName] = swatches; - // add default swatch into theme - pmUtils.themesList[themeName].unshift('default'); - // update themes.json in sandbox - $.rib.fsUtils.write('/themes.json', - JSON.stringify(pmUtils.themesList)); + + type = getFileType(themeFile.name); + switch (type) { + case 'css': + singleCssHandler(themeFile.name, themeFile, handler); + break; + case 'zip': + reader = new FileReader(); + reader.onloadend = function (e) { + var zip, data, cssRule, cssData, copyFiles = []; + // get result data from reader + data = e.target.result; + cssRule = /(\.min\.css)$/i; + try { + zip = new ZipFile(data); + } catch (e) { + console.warn("Failed to parse imported file as zip."); } - } catch(e) { - alert(e.stack); - return false; - } - }); - }); + if (zip && zip.filelist) { + zip.filelist.forEach(function (zipInfo, idx, array) { + if (cssRule.test(zipInfo.filename)) { + cssData = zip.extract(zipInfo.filename); + // write themeFile to sandbox + singleCssHandler(zipInfo.filename.replace(/^themes\//i,""), + cssData, handler); + } + }); + } + }; + reader.onError = function () { + console.error("Read imported file error."); + }; + reader.readAsBinaryString(themeFile); + break; + case 'default': + console.warn("unsupported file type, please upload css or zip file"); + break; + } }; /** @@ -1100,6 +1144,103 @@ $(function () { return themes; }; + /** + * import single css file to project + * + * @param {String} themeName imported name of theme file + * @param {String} content content of theme + * @param {Function} handler callback after handling theme + */ + function singleCssHandler (themeName, content, handler) { + var parseSwatches = function (buffer) { + var swatches = [], swatch, arr =[], i, + re = /\.ui-bar-[a-z]/g; + arr = buffer.match(re); + for (i = 0; i < arr.length; i++) { + swatch = arr[i].replace(/\.ui-bar-/g, ""); + // if swatch is not found in swatcher list, add it into swatches + if (swatch && jQuery.inArray(swatch, swatches) === -1) { + swatches.push(swatch); + } + } + return swatches; + }; + // write theme to sandbox + var writeThemeFile = function (themeName, content, handler) { + $.rib.fsUtils.write('/themes/' + themeName, content, function () { + //read file to buffer + $.rib.fsUtils.read('/themes/' + themeName, function (buffer) { + var theme, swatches = []; + try { + // split suffix of '.css' and '.min.css' + theme = themeName.replace(/(\.min.css|\.css)$/g, ""); + swatches = parseSwatches(buffer); + if (swatches.length) { + pmUtils.themesList[theme] = swatches; + // add default swatch into theme + pmUtils.themesList[theme].unshift('default'); + // update themes.json in sandbox + $.rib.fsUtils.write('/themes.json', + JSON.stringify(pmUtils.themesList)); + handler(); + } + } catch(e) { + alert(e.stack); + } + }); + //update allThemes + $.rib.pmUtils.allThemes.push(themeName); + }); + }; + + // firstly we check whether name of imported theme file + // exists in the projects + // If exists, a confirm dialog pup up for user to select + // if user select "yes", orignal theme file will be deleted + // else do nothing, return directly + // else upload new theme files + var msg, minifiedRule = /(\.min\.css)$/i, + theme = themeName.replace(/(\.css|\.min.css)$/g, ""); + if ($.rib.pmUtils.themesList.hasOwnProperty(theme) || + theme === "Default") { + if (theme === "Default") { + msg = "Defauls used for JQuery Mobile default theme" + + "(jquery.mobile.theme-1.1.0.css), please rename" + + " imported theme"; + $.rib.msgbox(msg, {"OK": null}); + return; + } else { + msg = "There is " + theme + " theme existed in projects. " + + "Would you like to replace it?"; + $.rib.confirm(msg, + // if user select "OK" button, replace old one with new theme + function () { + var anotherThemeFile; + if (minifiedRule.test(themeName)) { + anotherThemeFile = themeName.replace(/\.min\.css$/g, "\.css"); + } else { + anotherThemeFile = themeName.replace(/\.css$/g, ".min.css"); + } + if (jQuery.inArray(anotherThemeFile, pmUtils.allThemes) !== -1) { + $.rib.fsUtils.rm('/themes/' + anotherThemeFile, + function () { + var idx, allThemes = $.rib.pmUtils.allThemes; + idx = allThemes.indexOf(anotherThemeFile); + if(idx!=-1) { + allThemes.splice(idx, 1); + } + writeThemeFile(themeName, content, handler); + }); + } else { + writeThemeFile(themeName, content, handler); + } + }); + } + } else { + writeThemeFile(themeName, content, handler); + } + } + /************ export pmUtils to $.rib **************/ $.rib.pmUtils = pmUtils; }); diff --git a/src/js/serialize.js b/src/js/serialize.js index 64f5b2cc..89af4beb 100644 --- a/src/js/serialize.js +++ b/src/js/serialize.js @@ -425,11 +425,23 @@ $(function () { } function getDesignHeaders(design, useSandboxUrl) { - var i, props, el, designRoot, headers; + var i, props, el, designRoot, headers, toCorrectPath; designRoot = design || ADM.getDesignRoot(); headers = []; props = designRoot.getProperty('metas'); + toCorrectPath = function (header) { + var path = header.value; + // If need to use sandbox url + if (header.inSandbox) { + if (useSandboxUrl) { + path = toSandboxUrl(path); + } else { + path = path.replace(/^\//, ''); + } + } + return path; + }; for (i in props) { // Skip design only header properties if (props[i].hasOwnProperty('designOnly') && props[i].designOnly) { @@ -444,12 +456,7 @@ $(function () { if ((typeof props[i].value !== 'string') || (props[i].value.length <= 0)) { continue; } - // If need to use sandbox url - if (useSandboxUrl && props[i].inSandbox) { - el = el + '="' + toSandboxUrl(props[i].value) + '"'; - } else { - el = el + '="' + props[i].value + '"'; - } + el = el + '="' + toCorrectPath(props[i]) + '"'; if (props[i].hasOwnProperty('content')) { el = el + ' content="' + props[i].content + '"'; } @@ -470,11 +477,7 @@ $(function () { } el = ''; headers.push(el); } @@ -492,11 +495,7 @@ $(function () { } el = ''; headers.push(el); } @@ -659,7 +658,7 @@ $(function () { for (p in matched) { files.push({ "src": toSandboxUrl(matched[p]), - "dst": matched[p] + "dst": matched[p].value ? matched[p].value : matched[p] }); } }); @@ -730,19 +729,23 @@ $(function () { } function toSandboxUrl(path, pid) { - var projectDir, fullPath; + var projectDir, pathStr; pid = pid || $.rib.pmUtils.getActive(); projectDir = $.rib.pmUtils.getProjectDir(pid); - if (typeof path !== "string") { - console.error("Invalid path in toSandboxUrl: " + path); + if ((path instanceof Object) && path.inSandbox) { + pathStr = path.value; + } else { + pathStr = path; + } + if (typeof pathStr !== "string") { + console.error("Invalid path in toSandboxUrl: " + pathStr); return null; } - fullPath = path; // If the first char is '/', then it will be the absolute path in sandbox - if (path[0] !== '/' && projectDir) { - fullPath = projectDir + path; + if (pathStr[0] !== '/' && projectDir) { + pathStr = projectDir + pathStr; } - return $.rib.fsUtils.pathToUrl(fullPath); + return $.rib.fsUtils.pathToUrl(pathStr); } function indexOfArray(array, value) { @@ -907,6 +910,48 @@ $(function () { }, error); } + /** + * Save event handler codes to main.js + * + * @param {ADMNode} design ADM design root to be serialized. + * + * @return {None} as same as addCustomFile. + */ + function saveEventHandlers(design) { + var results, id, matchedProps, eventCode, eventName, + design = design || ADM.getDesignRoot(), + jsFileName = 'js/main.js', jsType = 'js', + jsHeader = '$(document).ready(function(e) {\n', + jsContent = '', + jsFooter = '});'; + + // Regenerate the whole event javascript codes + // and save to sandbox. + results = design.findNodesByProperty( + {'type': 'event', 'value': new RegExp('.+')} + ); + $.each(results, function(index, result) { + id = result.node.getProperty('id'); + if (!id) + return + matchedProps = result.properties; + for (eventName in matchedProps) { + // Append the event code to the whole js code content. + jsContent += '$("#' + id + '").bind("' + eventName + '", function(e) {' + + '\n' + matchedProps[eventName] + '\n' + + '});\n\n'; + } + }); + if (!jsContent) { + removeSandboxHeader(jsType, jsFileName); + $.rib.fsUtils.rm(jsFileName); + return null; + } + return addCustomFile( + jsFileName, jsType, js_beautify(jsHeader + jsContent + jsFooter) + ); + } + /***************** export functions out *********************/ // Export serialization functions into $.rib namespace $.rib.generateHTML = generateHTML; @@ -919,4 +964,5 @@ $(function () { $.rib.addSandboxHeader = addSandboxHeader; $.rib.removeSandboxHeader = removeSandboxHeader; $.rib.addCustomFile = addCustomFile; + $.rib.saveEventHandlers = saveEventHandlers; }); diff --git a/src/js/views/layout.js b/src/js/views/layout.js index e925c36c..98fa2f0f 100644 --- a/src/js/views/layout.js +++ b/src/js/views/layout.js @@ -428,15 +428,6 @@ // these lines. $(domNode).removeAttr('disabled'); $(domNode).children().removeAttr('disabled'); - - // Set default src for empty image to make them show up - // TODO: This case may need to improve, such as we can show a box - // with "empty image" message to notice the user - if ((admNode.getType() === "Image") && (!admNode.getProperty('src'))) { - if ($(domNode).is('img')) { - $(domNode).attr('src', "src/css/images/widgets/tizen_image.svg"); - } - } } }); })(jQuery); diff --git a/src/js/views/outline.js b/src/js/views/outline.js index 855bfa72..765b39e9 100644 --- a/src/js/views/outline.js +++ b/src/js/views/outline.js @@ -102,7 +102,6 @@ } widget.removeNode(event.node); widget.addNode(event.node); - widget.setSelected(widget._getSelected()); break; default: console.warning('Unknown type of modelUpdated event:' @@ -157,6 +156,8 @@ }, _getSelected: function () { var model = this.options.model; + if (!model) + return null; return model.getSelectedNode() || model.getActivePage(); }, _renderPageNode: function (domNode, node) { diff --git a/src/js/views/project.js b/src/js/views/project.js index 99c33a1b..85a35f1f 100644 --- a/src/js/views/project.js +++ b/src/js/views/project.js @@ -249,18 +249,26 @@ .appendTo(projectDialog, this); $(document).delegate('#uploadTheme', "click", function () { - $.rib.fsUtils.upload('css', projectDialog, function(file) { - pmUtils.uploadTheme(file); - //if file.name isn't in theme select list, add it to the list - //else do nothing - var themeName = file.name.replace(/.css$/g, ""); - themeNames = pmUtils.getAllThemes(); - if (jQuery.inArray(themeName, themeNames) === -1) { - themeNames.push(themeName); - $('') - .appendTo('#themePicker', projectDialog); - } + $.rib.fsUtils.upload('any', projectDialog, function(file) { + var handler = function () { + var themeName, themeList = []; + + // get current themes from select element + projectDialog.find('#themePicker option').each(function () { + themeList.push($(this).val()); + }); + for (themeName in pmUtils.themesList) { + // if imported theme file isn't in theme select list, add it + // to the list else do nothing + if (jQuery.inArray(themeName, themeList) === -1) { + $('') + .appendTo('#themePicker', projectDialog); + } + } + }; + + pmUtils.uploadTheme(file, handler); }); }); projectDialog.dialog({ @@ -411,7 +419,7 @@ // and open it, create a new project in no project case if (!$.rib.pmUtils.getActive()) { $(document.body).one("tabsselect", function (e, tab) { - if (tab.index === 1) { + if (tab.index !== 0) { $.rib.pmUtils.showLastOpened(); } }); diff --git a/src/js/views/property.js b/src/js/views/property.js index d19ef861..9157a644 100644 --- a/src/js/views/property.js +++ b/src/js/views/property.js @@ -39,6 +39,10 @@ * 0.4); el.height(newHeight); }); + this.element.delegate('*', 'focus', function(e){ + window.focusElement = this; + e.stopPropagation(); + }); return this; }, @@ -80,13 +84,28 @@ widget.refresh(event,widget); }, + _setProperty: function(property, value) { + var viewId = property + '-value'; + this.element.find("#" + viewId).val(value); + }, + _modelUpdatedHandler: function(event, widget) { + var affectedWidget, id; + widget = widget || this; - if (event && (event.type === "propertyChanged" && - event.node.getType() === 'Design')) { - return; + if (event && event.type === "propertyChanged") { + if (event.node.getType() === 'Design') { + return; + } + widget._setProperty(event.property, event.newValue); } else { widget.refresh(event,widget); + if (event.type === 'propertyChanged') { + id = event.property + '-value'; + affectedWidget = widget.element.find('#' + id); + affectedWidget[0].scrollIntoViewIfNeeded(); + affectedWidget.effect("highlight", {}, 1000); + } } }, @@ -94,9 +113,10 @@ var labelId, labelVal, valueId, valueVal, count, widget = this, type, i, child, index, propType, p, props, options, code, o, propertyItems, label, value, + design = ADM.getDesignRoot(), title = this.element.parent().find('.property_title'), content = this.element.find('.property_content'), - continueToDelete, container; + continueToDelete, buttonsContainer, container, range, min, max; // Clear the properties pane when nothing is selected if (node === null || node === undefined) { @@ -163,24 +183,49 @@ value.find("#" + valueId).attr("checked", "checked"); } break; + case "integer": + range = BWidget.getPropertyRange(type, p); + if (range) { + min = range.split('-')[0]; + max = range.split('-')[1]; + $('') + .addClass('title labelInput') + .attr({ + id: valueId, + min: min, + max: max + }) + .change(function(event) { + if( parseInt(this.value) > parseInt(this.max) || + parseInt(this.value) < parseInt(this.min)) { + $(this).effect("highlight", {color: "red"}, 1000); + event.stopImmediatePropagation(); + this.value = valueVal; + } + }) + .appendTo(value); + //set default value + value.find('#' + valueId).val(valueVal); + } + break; case "url-uploadable": $('') .attr('id', valueId) .addClass('title labelInput') .appendTo(value); //set default value - value.find('#' + valueId).val(valueVal); + value.find('#' + valueId).val(valueVal.value); $('') .addClass('buttonStyle') - .click(function (e) { - var target, saveDir; - target = $(this).prev("input:text"); - saveDir = $.rib.pmUtils.ProjectDir + "/" + $.rib.pmUtils.getActive() + "/images/"; + .click({node:node, property:p}, function (e) { + var saveDir = $.rib.pmUtils.getProjectDir() + "images/"; $.rib.fsUtils.upload("image", $(this).parent(), function(file) { // Write uploaded file to sandbox $.rib.fsUtils.write(saveDir + file.name, file, function (newFile) { - target.val("images/" + newFile.name); - target.trigger('change'); + ADM.setProperty(e.data.node, e.data.property, { + inSandbox: true, + value: "images/" + newFile.name + }); }); }); }).appendTo(value); @@ -248,9 +293,8 @@ .end().end() .append('') .children().eq(3) - .append('') + .append('
Delete
') .children(':first') - .attr('src', "src/css/images/deleteButton_up.png") // add delete option handler .click(function(e) { try { @@ -380,14 +424,13 @@ .click({'p': p, 'value': value}, function(e) { var o, items = "", pages, id, value = e.data.value, p = e.data.p; - items += '
  • previous page
  • '; container = node.getParent(); while (container !== null && container.getType() !== "Page") { container = container.getParent(); } - pages = ADM.getDesignRoot().getChildren(); + pages = design.getChildren(); for (o = 0; o < pages.length; o++) { if (pages[o] === container) { continue; @@ -466,12 +509,14 @@ if (selected.length > 0) { $(this).val(selected.text()); } - value = validValue($(this), - BWidget.getPropertyType(node.getType(), updated)); + value = validValue( + node, $(this), + BWidget.getPropertyType(node.getType(), updated) + ); ret = ADM.setProperty(node, updated, value); type = node.getType(); if (ret.result === false) { - $(this).val(node.getProperty(updated)); + $(this).effect("highlight", {color: "red"}, 1000).val(node.getProperty(updated)); } else if (type === "Button" && value === "previous page") { ADM.setProperty(node, "opentargetas", "default"); @@ -481,22 +526,355 @@ }); } - // add delete element button - $('
    ') + // add buttons container + buttonsContainer = $('
    ') .addClass('property_footer') - .children('button') + .appendTo(content) + .end(); + + // Add event handler button + $('') + .addClass('buttonStyle') + .attr('id', "eventHandlerElement") + .appendTo(buttonsContainer) + .bind('click', function(e) { + var generateEventSelectElement, generateEventHandlersList, + removeEventHandler, eventLinkClicked; + var formContainer, leftPannel, leftPannelContainer, + rightPannel, eventElement, eventSelectElement, + eventEditorContainer, eventEditor, formElement, + jsCode, eventHandlersList, id, + uniqueIdName = 'id'; + + // If node have no ID property, then return directly. + if(typeof(BWidget.propertyExists(node.getType(), uniqueIdName)) == 'undefined') { + alert('Event handler must be using with the element have ID property.'); + return false; + }; + + /* + * Call back functions. + */ + + // Remove event handler + removeEventHandler = function(e) { + e.preventDefault(); + var eventName = $(this).parent().attr('rel'); + $.rib.confirm( + 'Are you sure you want to delete the ' + + eventName + + ' event handler?', + function() { + node.setProperty(eventName, ''); + if (eventElement.val() == eventName) + eventEditor.setValue(''); + formElement.trigger('submit'); + } + ); + } + + // Event link clicked callback + eventLinkClicked = function(e) { + e.preventDefault(); + var eventName = $(this).parent().attr('rel'); + eventSelectElement.val(eventName); + formElement.trigger('submit'); + } + + /* + * Elements generation functions. + */ + + // Generate the event property select options. + generateEventSelectElement = function(selectElement, matchedProps) { + var selected, newSelectElement, result, matchedProps, + optionElement, eventName, optionsGroup; + + // Store the old selected event. + selected = selectElement.val(); + + // Clean up the origin options. + selectElement.find('option').remove(); + + // Search event properties + if (!matchedProps) + matchedProps = node.getMatchingProperties( + {'type': 'event'} + ); + + // Added a initial blank option; + $('') + .addClass('cm-inactive') + .appendTo(selectElement); + + // TODO: Classify the events and use optgroup to + // genereate it. + /* + optionsGroup = $( + '' + ) + .appendTo(selectElement); + */ + + // Generate event select options. + for (eventName in matchedProps) { + optionElement = $('