├── CNAME ├── LICENSE ├── LICENSE.jquery ├── LICENSE.jqueryui ├── README ├── border-image-generator.js ├── css ├── border-image-generator-standalone.css └── border-image-generator.css ├── expander.js ├── history-handler.js ├── index.html └── lib ├── jquery.ui.resizable.min.js ├── json2.js └── user-image-cache └── user-image-cache.js /CNAME: -------------------------------------------------------------------------------- 1 | border-image.com -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Kevin Decker, http://www.incaseofstairs.com/ 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | - Neither the name of the Kevin Decker nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | -------------------------------------------------------------------------------- /LICENSE.jquery: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 John Resig, http://jquery.com/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /LICENSE.jqueryui: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Paul Bakaus, http://jqueryui.com/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals (AUTHORS.txt, http://jqueryui.com/about) For exact 5 | contribution history, see the revision history and logs, available 6 | at http://jquery-ui.googlecode.com/svn/ 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | border-image-generator allows web developers to quickly and easily generate preview border-image styles. 2 | -------------------------------------------------------------------------------- /border-image-generator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Kevin Decker (http://www.incaseofstairs.com/) 3 | * See LICENSE for license information 4 | */ 5 | $(document).ready(function() { 6 | var pathToImage = $("#pathToImage"), 7 | editorEl = $("#editorEl"), 8 | imageEl = $("#imageEl"), 9 | dividers = $(".divider"), 10 | sliders = $(".slider"), 11 | fillEl = $("#fillCenter"), 12 | cssEl = $("#cssEl"), 13 | repeat = $(".repeat"), 14 | 15 | validImage = false, 16 | naturalSize = {}, 17 | 18 | state = { 19 | src: "http://www.w3.org/TR/css3-background/border.png", 20 | 21 | linkBorder: true, 22 | borderWidth: [0, 0, 0, 0], 23 | imageOffset: [27, 27, 27, 27], 24 | 25 | fill: true, 26 | setRepat: false, 27 | repeat: ["repeat", "repeat"], 28 | 29 | scaleFactor: 3 30 | }; 31 | 32 | var sliderMap = { 33 | imageTop: { array: "imageOffset", index: 0 }, 34 | imageRight: { array: "imageOffset", index: 1 }, 35 | imageBottom: { array: "imageOffset", index: 2 }, 36 | imageLeft: { array: "imageOffset", index: 3 }, 37 | 38 | borderTop: { array: "borderWidth", index: 0 }, 39 | borderRight: { array: "borderWidth", index: 1 }, 40 | borderBottom: { array: "borderWidth", index: 2 }, 41 | borderLeft: { array: "borderWidth", index: 3 } 42 | }, dividerMap = { 43 | dividerTop: { 44 | setValue: function(el) { state.imageOffset[0] = calcPixels($(el).position().top); }, 45 | updatePos: function(el) { $(el).css("top", state.imageOffset[0]*state.scaleFactor); } 46 | }, 47 | dividerRight: { 48 | setValue: function(el) { state.imageOffset[1] = calcPixels(editorEl.innerWidth() - $(el).position().left + 2); }, 49 | updatePos: function(el) { $(el).css("left", (editorEl.innerWidth() - 2 - state.imageOffset[1]*state.scaleFactor)); } 50 | }, 51 | dividerBottom: { 52 | setValue: function(el) { state.imageOffset[2] = calcPixels(editorEl.innerHeight() - $(el).position().top + 2); }, 53 | updatePos: function(el) { $(el).css("top", (editorEl.innerHeight() - 2 - state.imageOffset[2]*state.scaleFactor)); } 54 | }, 55 | dividerLeft: { 56 | setValue: function(el) { state.imageOffset[3] = calcPixels($(el).position().left); }, 57 | updatePos: function(el) { $(el).css("left", state.imageOffset[3]*state.scaleFactor); } 58 | } 59 | }, repeatMap = { 60 | repeatVertical: { index: 1 }, 61 | repeatHorizontal: { index: 0 } 62 | }; 63 | 64 | function calcPixels(pos) { 65 | return (pos / state.scaleFactor) | 0; 66 | } 67 | function updateFill() { 68 | fillEl[0].checked = !!state.fill; 69 | } 70 | function updateSliders() { 71 | $(".slider").each(function(index, el) { 72 | var map = sliderMap[el.id]; 73 | $(el).slider("option", "value", state[map.array][map.index]); 74 | }); 75 | } 76 | function updateDividers() { 77 | dividers.each(function(index, el) { 78 | dividerMap[el.id].updatePos(el); 79 | }); 80 | } 81 | function updateRepeat() { 82 | repeat.each(function(index, el) { 83 | var map = repeatMap[el.id]; 84 | $(el).val(state.repeat[map.index]); 85 | }); 86 | } 87 | function updateHash() { 88 | HistoryHandler.store(JSON.stringify(state)); 89 | } 90 | function joinValues(values, join) { 91 | var ret = []; 92 | if (values[3] !== undefined && values[3] !== values[1]) { 93 | ret.unshift(values[3]); 94 | } 95 | if (ret.length || (values[2] !== undefined && values[2] !== values[0])) { 96 | ret.unshift(values[2]); 97 | } 98 | if (ret.length || (values[1] !== undefined && values[1] !== values[0])) { 99 | ret.unshift(values[1]); 100 | } 101 | ret.unshift(values[0]); 102 | return ret.join(join || " "); 103 | } 104 | function updateCSS() { 105 | var borderImage = "", borderWidthStr = "", style = "", 106 | fillStr = state.fill ? " fill" : "", 107 | repeatStr = state.setRepeat ? " " + joinValues(state.repeat) : ""; 108 | 109 | if (validImage) { 110 | var img = "url(" + UserImageCache.getDisplayName() + ")", 111 | imageOffset = state.imageOffset, 112 | borderWidth = state.linkBorder ? state.imageOffset : state.borderWidth; 113 | 114 | borderImage = img + " " + joinValues(imageOffset); 115 | borderWidthStr = joinValues(borderWidth, "px ") + "px"; 116 | style = "border-style: solid;\n" 117 | + "border-width: " + borderWidthStr + ";\n" 118 | + "-moz-border-image: " + borderImage + repeatStr + ";\n" 119 | + "-webkit-border-image: " + borderImage + repeatStr + ";\n" 120 | + "-o-border-image: " + borderImage + repeatStr + ";\n" 121 | + "border-image: " + borderImage + fillStr + repeatStr + ";\n"; 122 | 123 | borderImage = "url(" + UserImageCache.getSrc() + ") " + joinValues(imageOffset); 124 | } 125 | 126 | $("#cssEl").html(style) 127 | .css("border-width", borderWidthStr) 128 | .css("-moz-border-image", borderImage + repeatStr) 129 | .css("-webkit-border-image", borderImage + repeatStr) 130 | .css("-o-border-image", borderImage + repeatStr) 131 | .css("border-image", borderImage + fillStr + repeatStr); 132 | } 133 | 134 | fillEl.change(function() { 135 | state.fill = this.checked; 136 | updateCSS(); 137 | updateHash(); 138 | }); 139 | 140 | sliders.slider({ 141 | max: 100, 142 | slide: function(event, ui) { 143 | var map = sliderMap[event.target.id]; 144 | state[map.array][map.index] = ui.value; 145 | 146 | updateCSS(); 147 | updateDividers(); 148 | }, 149 | stop: function() { 150 | updateHash(); 151 | } 152 | }); 153 | dividers.draggable({ 154 | containment: "parent", 155 | drag: function(event, ui) { 156 | dividerMap[event.target.id].setValue(event.target); 157 | updateCSS(); 158 | updateSliders(); 159 | }, 160 | stop: function() { 161 | updateHash(); 162 | } 163 | }); 164 | dividers.filter(":even").draggable("option", "axis", "y"); 165 | dividers.filter(":odd").draggable("option", "axis", "x"); 166 | 167 | repeat.change(function() { 168 | var map = repeatMap[this.id]; 169 | state.repeat[map.index] = $(this).val(); 170 | updateCSS(); 171 | updateHash(); 172 | }); 173 | 174 | UserImageCache.setImageEl(imageEl[0]); 175 | imageEl.load(function() { 176 | var img = this, 177 | natWidth = img.naturalWidth || img.width, 178 | natHeight = img.naturalHeight || img.height, 179 | width = natWidth*state.scaleFactor, 180 | height = natHeight*state.scaleFactor; 181 | 182 | // Ensure that the initial scale for the image is always smaller that the size of the screen 183 | if (width > window.innerWidth || height > window.innerHeight) { 184 | state.scaleFactor = Math.min(window.innerWidth/width, window.innerHeight/height); 185 | width = natWidth*state.scaleFactor; 186 | height = natHeight*state.scaleFactor; 187 | } 188 | 189 | naturalSize = { 190 | width: natWidth, 191 | height: natHeight 192 | }; 193 | 194 | // Correct for any HTTP escaping issues in the input 195 | state.src = UserImageCache.getEntryId(); 196 | pathToImage.val(UserImageCache.getDisplayName()); 197 | 198 | editorEl.width(width).height(height); 199 | editorEl.show(); 200 | 201 | $(".errorMsg").hide(); 202 | validImage = true; 203 | 204 | sliders.filter(":odd").slider("option", "max", natWidth); 205 | sliders.filter(":even").slider("option", "max", natHeight); 206 | updateSliders(); 207 | updateDividers(); 208 | updateCSS(); 209 | updateHash(); 210 | }); 211 | 212 | function errorHandler(code) { 213 | var msg; 214 | if (code === ImageList.NOT_FOUND) { 215 | msg = "Unable to find image. This may be due to an incorrect path name or a local file that has not been properly loaded."; 216 | } else if (code) { 217 | msg = "Failed to load image. Error code: " + code; 218 | } else { 219 | msg = "Unknown error occured loading image " + UserImageCache.getDisplayName(); 220 | } 221 | 222 | // Only show the message if the user as attempted to load an image 223 | if (UserImageCache.getEntryId()) { 224 | $(".errorMsg").html("*** " + msg).show(); 225 | } 226 | 227 | editorEl.hide(); 228 | validImage = false; 229 | 230 | updateCSS(); 231 | } 232 | imageEl.error(function() { errorHandler(); }); 233 | pathToImage.change(function(event) { 234 | // Clear the frame size so Opera can scale the editor down if the new image is smaller than the last 235 | editorEl.width("auto").height("auto"); 236 | UserImageCache.load(pathToImage.val(), errorHandler); 237 | }); 238 | 239 | function setFlag(name, value) { 240 | return function() { 241 | state[name] = value; 242 | updateCSS(); 243 | updateHash(); 244 | }; 245 | } 246 | $("#borderOptionsExpander").expander("#borderOptions", setFlag("linkBorder", false), setFlag("linkBorder", true)); 247 | $("#repeatOptionsExpander").expander("#repeatOptions", setFlag("setRepeat", true), setFlag("setRepeat", false)); 248 | 249 | editorEl.resizable({ 250 | reverseXAxis: true, 251 | handles: "s, w, sw", 252 | aspectRatio: true, 253 | resize: function() { 254 | state.scaleFactor = editorEl.innerWidth() / naturalSize.width; 255 | 256 | updateSliders(); 257 | updateDividers(); 258 | updateCSS(); 259 | }, 260 | stop: function() { 261 | updateHash(); 262 | } 263 | }); 264 | 265 | if (UserImageCache.isLocalSupported()) { 266 | $("body").bind("dragenter dragover", function(event) { 267 | // We have to cancel these events or we will not recieve the drop event 268 | event.preventDefault(); 269 | event.stopPropagation(); 270 | }); 271 | $("body").bind("drop", function(event) { 272 | event.preventDefault(); 273 | event.stopPropagation(); 274 | var dataTransfer = event.originalEvent.dataTransfer, 275 | file = dataTransfer.files[0]; 276 | 277 | UserImageCache.load(file, errorHandler); 278 | }); 279 | $("#localImage").bind("change", function(event) { 280 | var file = this.files[0]; 281 | 282 | UserImageCache.load(file, errorHandler); 283 | }); 284 | 285 | $("body").removeClass("no-local"); 286 | } 287 | 288 | $(".toggleStyle").click(function(event) { 289 | $("body").toggleClass("light"); 290 | var lightness = $("#lightnessStyle"); 291 | lightness[0].disabled = !lightness[0].disabled; 292 | event.preventDefault(); 293 | }); 294 | 295 | HistoryHandler.init(function(hash) { 296 | var prevScale = state.scaleFactor; 297 | 298 | if (hash) { 299 | $.extend(state, JSON.parse(hash)); 300 | } 301 | if ($("#borderOptions").is(":visible") === state.linkBorder) { 302 | $("#borderOptionsExpander").click(); 303 | } 304 | if ($("#repeatOptions").is(":visible") !== state.setRepeat) { 305 | $("#repeatOptionsExpander").click(); 306 | } 307 | 308 | updateFill(); 309 | 310 | if (UserImageCache.getEntryId() !== state.src) { 311 | // The other values will update when the image loads 312 | UserImageCache.load(state.src, errorHandler); 313 | } else if (prevScale !== state.scaleFactor) { 314 | imageEl.load(); 315 | } else { 316 | updateSliders(); 317 | updateDividers(); 318 | updateCSS(); 319 | } 320 | 321 | updateRepeat(); 322 | }); 323 | }); 324 | -------------------------------------------------------------------------------- /css/border-image-generator-standalone.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Kevin Decker (http://www.incaseofstairs.com/) 3 | * See LICENSE for license information 4 | */ 5 | 6 | /* Standalone Specific CSS */ 7 | a { 8 | color: inherit; 9 | text-decoration: none; 10 | } 11 | 12 | body.borderGenerator { 13 | background-color: #83ADC8; 14 | font-size: 9pt; 15 | font-family: trebuchet ms,arial,helvetica,sans-serif; 16 | margin: 0; 17 | } 18 | body.borderGenrator > div { 19 | margin: 0px; 20 | padding: 0px; 21 | } 22 | body.borderGenerator { 23 | color: white; 24 | } 25 | 26 | body.borderGenerator > .wrapper { 27 | margin: 0px auto; 28 | padding: 0; 29 | background-color: #131F27; 30 | 31 | width: 1000px; 32 | } 33 | 34 | body.borderGenerator .header { 35 | background-color: black; 36 | margin: 20px 0 0; 37 | } 38 | 39 | body.borderGenerator > .wrapper, 40 | body.borderGenerator > .wrapper > .header { 41 | -moz-border-radius: 10px 10px 0px 0px; 42 | -webkit-border-radius: 10px 10px 0px 0px; 43 | border-radius: 10px 10px 0px 0px; 44 | } 45 | 46 | body.borderGenerator .header > h1 { 47 | display: inline-block; 48 | 49 | margin: 0; 50 | padding: 5px 15px; 51 | 52 | font-size: 15pt; 53 | font-weight: normal; 54 | letter-spacing: -2px; 55 | } 56 | body.borderGenerator .header > .toggleStyle { 57 | float: right; 58 | padding: 10px 5px; 59 | } 60 | 61 | body.borderGenerator .content, 62 | body.borderGenerator .css { 63 | margin: 5px 15px 0px; 64 | overflow: hidden; 65 | } 66 | 67 | body.borderGenerator .css { 68 | margin-bottom: 5px; 69 | } 70 | 71 | body.borderGenerator .footer { 72 | background-color: black; 73 | padding: 10px 25px; 74 | clear: both; 75 | overflow: auto; 76 | text-align: center; 77 | } 78 | body.borderGenerator .header > a, 79 | body.borderGenerator .footer > a { 80 | color: #6598B8; 81 | } 82 | 83 | /* Light Styles */ 84 | body.borderGenerator.light > .wrapper { 85 | background-color: #ECE0D8; 86 | } 87 | 88 | body.borderGenerator.light .content, 89 | body.borderGenerator.light .css { 90 | color: black; 91 | } 92 | 93 | body.borderGenerator.light #editorEl { 94 | border: 2px solid black; 95 | } 96 | -------------------------------------------------------------------------------- /css/border-image-generator.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Kevin Decker (http://www.incaseofstairs.com/) 3 | * See LICENSE for license information 4 | */ 5 | 6 | .borderGenerator label { 7 | float: left; 8 | width: 10em; 9 | text-align: right; 10 | } 11 | 12 | .borderGenerator input[type="text"] { 13 | width: 250px; 14 | margin-left: 0.6em; 15 | } 16 | 17 | .borderGenerator > div > h2 { 18 | margin: 0px; 19 | padding: 0px; 20 | color: white; 21 | display: inline-block; 22 | } 23 | 24 | .borderGenerator .setupSection > div { 25 | padding: 5px 0px; 26 | } 27 | 28 | #borderOptionsExpander { 29 | display: inline-block; 30 | } 31 | 32 | #cssEl { 33 | white-space: pre; 34 | border-style: solid; 35 | } 36 | 37 | #editorEl { 38 | position: relative; 39 | float: right; 40 | margin-right: 25px; 41 | border: 2px solid white; 42 | } 43 | #editorEl > .ui-resizable-s, 44 | #editorEl > .ui-resizable-sw { 45 | bottom: -7px; 46 | } 47 | #editorEl > .ui-resizable-w, 48 | #editorEl > .ui-resizable-sw { 49 | left: -7px; 50 | } 51 | 52 | #imageEl { 53 | width: 100%; 54 | height: 100%; 55 | -ms-interpretation-mode: nearest-neighbor; 56 | image-rendering: -moz-crisp-edges; 57 | image-rendering: -webkit-optimize-contrast; 58 | } 59 | 60 | .borderGenerator .divider { 61 | background-color: red; 62 | opacity: 0.8; 63 | cursor: move; 64 | 65 | position: absolute; 66 | width: 2px; 67 | height: 2px; 68 | } 69 | 70 | .borderGenerator .top-divider, 71 | .borderGenerator .bottom-divider { 72 | width: 100%; 73 | left: 0px; 74 | } 75 | .borderGenerator .top-divider { 76 | top: 0px; 77 | } 78 | .borderGenerator .bottom-divider { 79 | bottom: 0px; 80 | } 81 | 82 | .borderGenerator .left-divider, 83 | .borderGenerator .right-divider { 84 | top: 0px; 85 | height: 100%; 86 | } 87 | .borderGenerator .left-divider { 88 | left: 0px; 89 | } 90 | .borderGenerator .right-divider { 91 | right: 0px; 92 | } 93 | 94 | .borderGenerator .slider { 95 | display: inline-block; 96 | width: 200px; 97 | margin: 0 0.6em; 98 | } 99 | 100 | .borderGenerator .ui-icon { 101 | background-image: url(http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.0/themes/ui-lightness/images/ui-icons_ffffff_256x240.png); 102 | display: inline-block; 103 | } 104 | 105 | .errorMsg { 106 | color: red; 107 | padding: 10px; 108 | } 109 | 110 | .no-local .localDependent { 111 | display: none; 112 | } 113 | 114 | /* File Input Click Handler: See http://www.quirksmode.org/dom/inputfile.html */ 115 | .fileInput { 116 | position: relative; 117 | } 118 | 119 | .fileInput > input[type="button"] { 120 | position: absolute; 121 | top: 0px; 122 | left: 0px; 123 | z-index: 1; 124 | } 125 | 126 | .fileInput > input[type='file'] { 127 | position: absolute; 128 | top: 0px; 129 | left: 0px; 130 | text-align: right; 131 | opacity: 0; 132 | z-index: 2; 133 | } 134 | -------------------------------------------------------------------------------- /expander.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Kevin Decker (http://www.incaseofstairs.com/) 3 | * See LICENSE for license information 4 | */ 5 | (function($) { 6 | // Helper method that implements the expander UI 7 | $.fn.expander = function(child, onShow, onHide) { 8 | var self = this; 9 | 10 | self.toggle( 11 | function() { 12 | self.children("span").removeClass("ui-icon-triangle-1-e").addClass("ui-icon-triangle-1-s"); 13 | $(child).show(); 14 | 15 | onShow(); 16 | }, 17 | function() { 18 | self.children("span").removeClass("ui-icon-triangle-1-s").addClass("ui-icon-triangle-1-e"); 19 | $(child).hide(); 20 | 21 | onHide(); 22 | }); 23 | }; 24 | })(jQuery); 25 | -------------------------------------------------------------------------------- /history-handler.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Kevin Decker (http://www.incaseofstairs.com/) 3 | * See LICENSE for license information 4 | */ 5 | var HistoryHandler = (function() { 6 | var currentHash, callbackFn; 7 | 8 | function loadHash() { 9 | return decodeURIComponent((/#(.*)$/.exec(location.href) || [])[1] || ""); 10 | } 11 | function checkHistory(){ 12 | var hashValue = loadHash(); 13 | if(hashValue !== currentHash) { 14 | currentHash = hashValue; 15 | callbackFn(currentHash); 16 | } 17 | } 18 | 19 | return { 20 | init: function(callback) { 21 | callbackFn = callback; 22 | 23 | currentHash = loadHash(); 24 | callbackFn(currentHash); 25 | setInterval(checkHistory, 500); 26 | }, 27 | store: function(state) { 28 | currentHash = state; 29 | location = "#" + encodeURIComponent(state); 30 | } 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | border-image-generator 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

border-image-generator

19 | [toggle style] 20 |
21 |
22 |

Editor

23 |
24 | 32 | 33 |
34 | 35 | 36 | 37 | 38 | 39 |
40 | 41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | 49 |
50 |
51 | 52 | Border Size 53 |
54 | 60 |
61 | 62 |
63 |
64 | 65 | Border Repeat 66 |
67 | 87 |
88 |
89 | 90 |
91 |

Preview

92 |
93 |
94 | 95 | 104 |
105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 118 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /lib/jquery.ui.resizable.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery UI 1.8pre 3 | * 4 | * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about) 5 | * Dual licensed under the MIT (MIT-LICENSE.txt) 6 | * and GPL (GPL-LICENSE.txt) licenses. 7 | * 8 | * http://docs.jquery.com/UI 9 | */ 10 | (function(c){c.widget("ui.resizable",c.ui.mouse,{widgetEventPrefix:"resize",options:{alsoResize:false,animate:false,animateDuration:"slow",animateEasing:"swing",aspectRatio:false,autoHide:false,containment:false,ghost:false,grid:false,handles:"e,s,se",helper:false,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1000},_create:function(){var e=this,j=this.options;this.element.addClass("ui-resizable");c.extend(this,{_aspectRatio:!!(j.aspectRatio),aspectRatio:j.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:j.helper||j.ghost||j.animate?j.helper||"ui-resizable-helper":null});if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)){if(/relative/.test(this.element.css("position"))&&c.browser.opera){this.element.css({position:"relative",top:"auto",left:"auto"})}this.element.wrap(c('
').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")}));this.element=this.element.parent().data("resizable",this.element.data("resizable"));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle=this.originalElement.css("resize");this.originalElement.css("resize","none");this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"}));this.originalElement.css({margin:this.originalElement.css("margin")});this._proportionallyResize()}this.handles=j.handles||(!c(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"});if(this.handles.constructor==String){if(this.handles=="all"){this.handles="n,e,s,w,se,sw,ne,nw"}var k=this.handles.split(",");this.handles={};for(var f=0;f');if(/sw|se|ne|nw/.test(h)){g.css({zIndex:++j.zIndex})}if("se"==h){g.addClass("ui-icon ui-icon-gripsmall-diagonal-se")}this.handles[h]=".ui-resizable-"+h;this.element.append(g)}}this._renderAxis=function(p){p=p||this.element;for(var m in this.handles){if(this.handles[m].constructor==String){this.handles[m]=c(this.handles[m],this.element).show()}if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var n=c(this.handles[m],this.element),o=0;o=/sw|ne|nw|se|n|s/.test(m)?n.outerHeight():n.outerWidth();var l=["padding",/ne|nw|n/.test(m)?"Top":/se|sw|s/.test(m)?"Bottom":/^e$/.test(m)?"Right":"Left"].join("");p.css(l,o);this._proportionallyResize()}if(!c(this.handles[m]).length){continue}}};this._renderAxis(this.element);this._handles=c(".ui-resizable-handle",this.element).disableSelection();this._handles.mouseover(function(){if(!e.resizing){if(this.className){var i=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)}e.axis=i&&i[1]?i[1]:"se"}});if(j.autoHide){this._handles.hide();c(this.element).addClass("ui-resizable-autohide").hover(function(){c(this).removeClass("ui-resizable-autohide");e._handles.show()},function(){if(!e.resizing){c(this).addClass("ui-resizable-autohide");e._handles.hide()}})}this._mouseInit()},destroy:function(){this._mouseDestroy();var d=function(f){c(f).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){d(this.element);var e=this.element;e.after(this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")})).remove()}this.originalElement.css("resize",this.originalResizeStyle);d(this.originalElement);return this},_mouseCapture:function(e){var f=false;for(var d in this.handles){if(c(this.handles[d])[0]==e.target){f=true}}return !this.options.disabled&&f},_mouseStart:function(f){var i=this.options,e=this.element.position(),d=this.element;this.resizing=true;this.documentScroll={top:c(document).scrollTop(),left:c(document).scrollLeft()};if(d.is(".ui-draggable")||(/absolute/).test(d.css("position"))){d.css({position:"absolute",top:e.top,left:e.left})}if(c.browser.opera&&(/relative/).test(d.css("position"))){d.css({position:"relative",top:"auto",left:"auto"})}this._renderProxy();var j=b(this.helper.css("left")),g=b(this.helper.css("top"));if(i.containment){j+=c(i.containment).scrollLeft()||0;g+=c(i.containment).scrollTop()||0}this.offset=this.helper.offset();this.position={left:j,top:g};this.size=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalSize=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalPosition={left:j,top:g};this.sizeDiff={width:d.outerWidth()-d.width(),height:d.outerHeight()-d.height()};this.originalMousePosition={left:f.pageX,top:f.pageY};this.aspectRatio=(typeof i.aspectRatio=="number")?i.aspectRatio:((this.originalSize.width/this.originalSize.height)||1);var h=c(".ui-resizable-"+this.axis).css("cursor");c("body").css("cursor",h=="auto"?this.axis+"-resize":h);d.addClass("ui-resizable-resizing");this._propagate("start",f);return true},_mouseDrag:function(d){var g=this.helper,f=this.options,l={},p=this,i=this.originalMousePosition,m=this.axis;var q=(d.pageX-i.left)||0,n=(d.pageY-i.top)||0;var h=this._change[m];if(!h){return false}var k=h.apply(this,[d,q,n]),j=c.browser.msie&&c.browser.version<7,e=this.sizeDiff;if(this._aspectRatio||d.shiftKey){k=this._updateRatio(k,d)}k=this._respectSize(k,d);this._propagate("resize",d);g.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"});if(!this._helper&&this._proportionallyResizeElements.length){this._proportionallyResize()}this._updateCache(k);this._trigger("resize",d,this.ui());return false},_mouseStop:function(g){this.resizing=false;var h=this.options,l=this;if(this._helper){var f=this._proportionallyResizeElements,d=f.length&&(/textarea/i).test(f[0].nodeName),e=d&&c.ui.hasScroll(f[0],"left")?0:l.sizeDiff.height,j=d?0:l.sizeDiff.width;var m={width:(l.size.width-j),height:(l.size.height-e)},i=(parseInt(l.element.css("left"),10)+(l.position.left-l.originalPosition.left))||null,k=(parseInt(l.element.css("top"),10)+(l.position.top-l.originalPosition.top))||null;if(!h.animate){this.element.css(c.extend(m,{top:k,left:i}))}l.helper.height(l.size.height);l.helper.width(l.size.width);if(this._helper&&!h.animate){this._proportionallyResize()}}c("body").css("cursor","auto");this.element.removeClass("ui-resizable-resizing");this._propagate("stop",g);if(this._helper){this.helper.remove()}return false},_updateCache:function(d){var e=this.options;this.offset=this.helper.offset();if(a(d.left)){this.position.left=d.left}if(a(d.top)){this.position.top=d.top}if(a(d.height)){this.size.height=d.height}if(a(d.width)){this.size.width=d.width}},_updateRatio:function(g,f){var h=this.options,i=this.position,e=this.size,d=this.axis;if(g.height){g.width=(e.height*this.aspectRatio)}else{if(g.width){g.height=(e.width/this.aspectRatio)}}if(d=="sw"){g.left=i.left+(e.width-g.width);g.top=null}if(d=="nw"){g.top=i.top+(e.height-g.height);g.left=i.left+(e.width-g.width)}return g},_respectSize:function(k,f){var i=this.helper,h=this.options,q=this._aspectRatio||f.shiftKey,p=this.axis,s=a(k.width)&&h.maxWidth&&(h.maxWidthk.width),r=a(k.height)&&h.minHeight&&(h.minHeight>k.height);if(g){k.width=h.minWidth}if(r){k.height=h.minHeight}if(s){k.width=h.maxWidth}if(l){k.height=h.maxHeight}var e=this.originalPosition.left+this.originalSize.width,n=this.position.top+this.size.height;var j=/sw|nw|w/.test(p),d=/nw|ne|n/.test(p);if(g&&j){k.left=e-h.minWidth}if(s&&j){k.left=e-h.maxWidth}if(r&&d){k.top=n-h.minHeight}if(l&&d){k.top=n-h.maxHeight}var m=!k.width&&!k.height;if(m&&!k.left&&k.top){k.top=null}else{if(m&&!k.top&&k.left){k.left=null}}return k},_proportionallyResize:function(){var j=this.options;if(!this._proportionallyResizeElements.length){return}var f=this.helper||this.element;for(var e=0;e');var d=c.browser.msie&&c.browser.version<7,f=(d?1:0),g=(d?2:-1);this.helper.addClass(this._helper).css({width:this.element.outerWidth()+g,height:this.element.outerHeight()+g,position:"absolute",left:this.elementOffset.left-f+"px",top:this.elementOffset.top-f+"px",zIndex:++h.zIndex});this.helper.appendTo("body").disableSelection()}else{this.helper=this.element}},_change:{e:function(g,e,d){if(this.options.reverseXAxis){var f=this.originalSize,h=this.originalPosition;return{left:h.left-e,width:f.width+e}}else{return{width:this.originalSize.width+e}}},w:function(g,e,d){if(this.options.reverseXAxis){return{width:this.originalSize.width-e}}else{var f=this.originalSize,h=this.originalPosition;return{left:h.left+e,width:f.width-e}}},n:function(g,e,d){if(this.options.reverseYAxis){return{height:this.originalSize.height-d}}else{var f=this.originalSize,h=this.originalPosition;return{top:h.top+d,height:f.height-d}}},s:function(g,e,d){if(this.options.reverseYAxis){var f=this.originalSize,h=this.originalPosition;return{top:h.top-d,height:f.height+d}}else{return{height:this.originalSize.height+d}}},se:function(f,e,d){return c.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[f,e,d]))},sw:function(f,e,d){return c.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[f,e,d]))},ne:function(f,e,d){return c.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[f,e,d]))},nw:function(f,e,d){return c.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[f,e,d]))}},_propagate:function(e,d){c.ui.plugin.call(this,e,[d,this.ui()]);(e!="resize"&&this._trigger(e,d,this.ui()))},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}});c.extend(c.ui.resizable,{version:"1.8pre"});c.ui.plugin.add("resizable","alsoResize",{start:function(e,f){var d=c(this).data("resizable"),h=d.options;var g=function(i){c(i).each(function(){c(this).data("resizable-alsoresize",{width:parseInt(c(this).width(),10),height:parseInt(c(this).height(),10),left:parseInt(c(this).css("left"),10),top:parseInt(c(this).css("top"),10)})})};if(typeof(h.alsoResize)=="object"&&!h.alsoResize.parentNode){if(h.alsoResize.length){h.alsoResize=h.alsoResize[0];g(h.alsoResize)}else{c.each(h.alsoResize,function(i,j){g(i)})}}else{g(h.alsoResize)}},resize:function(f,h){var e=c(this).data("resizable"),i=e.options,g=e.originalSize,k=e.originalPosition;var j={height:(e.size.height-g.height)||0,width:(e.size.width-g.width)||0,top:(e.position.top-k.top)||0,left:(e.position.left-k.left)||0},d=function(l,m){c(l).each(function(){var p=c(this),q=c(this).data("resizable-alsoresize"),o={},n=m&&m.length?m:["width","height","top","left"];c.each(n||["width","height","top","left"],function(r,t){var s=(q[t]||0)+(j[t]||0);if(s&&s>=0){o[t]=s||null}});if(/relative/.test(p.css("position"))&&c.browser.opera){e._revertToRelativePosition=true;p.css({position:"absolute",top:"auto",left:"auto"})}p.css(o)})};if(typeof(i.alsoResize)=="object"&&!i.alsoResize.nodeType){c.each(i.alsoResize,function(l,m){d(l,m)})}else{d(i.alsoResize)}},stop:function(e,f){var d=c(this).data("resizable");if(d._revertToRelativePosition&&c.browser.opera){d._revertToRelativePosition=false;el.css({position:"relative"})}c(this).removeData("resizable-alsoresize-start")}});c.ui.plugin.add("resizable","animate",{stop:function(h,m){var n=c(this).data("resizable"),i=n.options;var g=n._proportionallyResizeElements,d=g.length&&(/textarea/i).test(g[0].nodeName),e=d&&c.ui.hasScroll(g[0],"left")?0:n.sizeDiff.height,k=d?0:n.sizeDiff.width;var f={width:(n.size.width-k),height:(n.size.height-e)},j=(parseInt(n.element.css("left"),10)+(n.position.left-n.originalPosition.left))||null,l=(parseInt(n.element.css("top"),10)+(n.position.top-n.originalPosition.top))||null;n.element.animate(c.extend(f,l&&j?{top:l,left:j}:{}),{duration:i.animateDuration,easing:i.animateEasing,step:function(){var o={width:parseInt(n.element.css("width"),10),height:parseInt(n.element.css("height"),10),top:parseInt(n.element.css("top"),10),left:parseInt(n.element.css("left"),10)};if(g&&g.length){c(g[0]).css({width:o.width,height:o.height})}n._updateCache(o);n._propagate("resize",h)}})}});c.ui.plugin.add("resizable","containment",{start:function(e,q){var s=c(this).data("resizable"),i=s.options,k=s.element;var f=i.containment,j=(f instanceof c)?f.get(0):(/parent/.test(f))?k.parent().get(0):f;if(!j){return}s.containerElement=c(j);if(/document/.test(f)||f==document){s.containerOffset={left:0,top:0};s.containerPosition={left:0,top:0};s.parentData={element:c(document),left:0,top:0,width:c(document).width(),height:c(document).height()||document.body.parentNode.scrollHeight}}else{var m=c(j),h=[];c(["Top","Right","Left","Bottom"]).each(function(p,o){h[p]=b(m.css("padding"+o))});s.containerOffset=m.offset();s.containerPosition=m.position();s.containerSize={height:(m.innerHeight()-h[3]),width:(m.innerWidth()-h[1])};var n=s.containerOffset,d=s.containerSize.height,l=s.containerSize.width,g=(c.ui.hasScroll(j,"left")?j.scrollWidth:l),r=(c.ui.hasScroll(j)?j.scrollHeight:d);s.parentData={element:j,left:n.left,top:n.top,width:g,height:r}}},resize:function(f,p){var s=c(this).data("resizable"),h=s.options,e=s.containerSize,n=s.containerOffset,l=s.size,m=s.position,q=s._aspectRatio||f.shiftKey,d={top:0,left:0},g=s.containerElement;if(g[0]!=document&&(/static/).test(g.css("position"))){d=n}if(m.left<(s._helper?n.left:0)){s.size.width=s.size.width+(s._helper?(s.position.left-n.left):(s.position.left-d.left));if(q){s.size.height=s.size.width/h.aspectRatio}s.position.left=h.helper?n.left:0}if(m.top<(s._helper?n.top:0)){s.size.height=s.size.height+(s._helper?(s.position.top-n.top):s.position.top);if(q){s.size.width=s.size.height*h.aspectRatio}s.position.top=s._helper?n.top:0}s.offset.left=s.parentData.left+s.position.left;s.offset.top=s.parentData.top+s.position.top;var k=Math.abs((s._helper?s.offset.left-d.left:(s.offset.left-d.left))+s.sizeDiff.width),r=Math.abs((s._helper?s.offset.top-d.top:(s.offset.top-n.top))+s.sizeDiff.height);var j=s.containerElement.get(0)==s.element.parent().get(0),i=/relative|absolute/.test(s.containerElement.css("position"));if(j&&i){k-=s.parentData.left}if(k+s.size.width>=s.parentData.width){s.size.width=s.parentData.width-k;if(q){s.size.height=s.size.width/s.aspectRatio}}if(r+s.size.height>=s.parentData.height){s.size.height=s.parentData.height-r;if(q){s.size.width=s.size.height*s.aspectRatio}}},stop:function(e,m){var p=c(this).data("resizable"),f=p.options,k=p.position,l=p.containerOffset,d=p.containerPosition,g=p.containerElement;var i=c(p.helper),q=i.offset(),n=i.outerWidth()-p.sizeDiff.width,j=i.outerHeight()-p.sizeDiff.height;if(p._helper&&!f.animate&&(/relative/).test(g.css("position"))){c(this).css({left:q.left-d.left-l.left,width:n,height:j})}if(p._helper&&!f.animate&&(/static/).test(g.css("position"))){c(this).css({left:q.left-d.left-l.left,width:n,height:j})}}});c.ui.plugin.add("resizable","ghost",{start:function(f,g){var d=c(this).data("resizable"),h=d.options,e=d.size;d.ghost=d.originalElement.clone();d.ghost.css({opacity:0.25,display:"block",position:"relative",height:e.height,width:e.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof h.ghost=="string"?h.ghost:"");d.ghost.appendTo(d.helper)},resize:function(e,f){var d=c(this).data("resizable"),g=d.options;if(d.ghost){d.ghost.css({position:"relative",height:d.size.height,width:d.size.width})}},stop:function(e,f){var d=c(this).data("resizable"),g=d.options;if(d.ghost&&d.helper){d.helper.get(0).removeChild(d.ghost.get(0))}}});c.ui.plugin.add("resizable","grid",{resize:function(d,l){var n=c(this).data("resizable"),g=n.options,j=n.size,h=n.originalSize,i=n.originalPosition,m=n.axis,k=g._aspectRatio||d.shiftKey;g.grid=typeof g.grid=="number"?[g.grid,g.grid]:g.grid;var f=Math.round((j.width-h.width)/(g.grid[0]||1))*(g.grid[0]||1),e=Math.round((j.height-h.height)/(g.grid[1]||1))*(g.grid[1]||1);if(/^(se|s|e)$/.test(m)){n.size.width=h.width+f;n.size.height=h.height+e}else{if(/^(ne)$/.test(m)){n.size.width=h.width+f;n.size.height=h.height+e;n.position.top=i.top-e}else{if(/^(sw)$/.test(m)){n.size.width=h.width+f;n.size.height=h.height+e;n.position.left=i.left-f}else{n.size.width=h.width+f;n.size.height=h.height+e;n.position.top=i.top-e;n.position.left=i.left-f}}}}});var b=function(d){return parseInt(d,10)||0};var a=function(d){return !isNaN(parseInt(d,10))}})(jQuery); -------------------------------------------------------------------------------- /lib/json2.js: -------------------------------------------------------------------------------- 1 | /* 2 | http://www.JSON.org/json2.js 3 | 2010-03-20 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | 12 | This code should be minified before deployment. 13 | See http://javascript.crockford.com/jsmin.html 14 | 15 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 16 | NOT CONTROL. 17 | 18 | 19 | This file creates a global JSON object containing two methods: stringify 20 | and parse. 21 | 22 | JSON.stringify(value, replacer, space) 23 | value any JavaScript value, usually an object or array. 24 | 25 | replacer an optional parameter that determines how object 26 | values are stringified for objects. It can be a 27 | function or an array of strings. 28 | 29 | space an optional parameter that specifies the indentation 30 | of nested structures. If it is omitted, the text will 31 | be packed without extra whitespace. If it is a number, 32 | it will specify the number of spaces to indent at each 33 | level. If it is a string (such as '\t' or ' '), 34 | it contains the characters used to indent at each level. 35 | 36 | This method produces a JSON text from a JavaScript value. 37 | 38 | When an object value is found, if the object contains a toJSON 39 | method, its toJSON method will be called and the result will be 40 | stringified. A toJSON method does not serialize: it returns the 41 | value represented by the name/value pair that should be serialized, 42 | or undefined if nothing should be serialized. The toJSON method 43 | will be passed the key associated with the value, and this will be 44 | bound to the value 45 | 46 | For example, this would serialize Dates as ISO strings. 47 | 48 | Date.prototype.toJSON = function (key) { 49 | function f(n) { 50 | // Format integers to have at least two digits. 51 | return n < 10 ? '0' + n : n; 52 | } 53 | 54 | return this.getUTCFullYear() + '-' + 55 | f(this.getUTCMonth() + 1) + '-' + 56 | f(this.getUTCDate()) + 'T' + 57 | f(this.getUTCHours()) + ':' + 58 | f(this.getUTCMinutes()) + ':' + 59 | f(this.getUTCSeconds()) + 'Z'; 60 | }; 61 | 62 | You can provide an optional replacer method. It will be passed the 63 | key and value of each member, with this bound to the containing 64 | object. The value that is returned from your method will be 65 | serialized. If your method returns undefined, then the member will 66 | be excluded from the serialization. 67 | 68 | If the replacer parameter is an array of strings, then it will be 69 | used to select the members to be serialized. It filters the results 70 | such that only members with keys listed in the replacer array are 71 | stringified. 72 | 73 | Values that do not have JSON representations, such as undefined or 74 | functions, will not be serialized. Such values in objects will be 75 | dropped; in arrays they will be replaced with null. You can use 76 | a replacer function to replace those with JSON values. 77 | JSON.stringify(undefined) returns undefined. 78 | 79 | The optional space parameter produces a stringification of the 80 | value that is filled with line breaks and indentation to make it 81 | easier to read. 82 | 83 | If the space parameter is a non-empty string, then that string will 84 | be used for indentation. If the space parameter is a number, then 85 | the indentation will be that many spaces. 86 | 87 | Example: 88 | 89 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 90 | // text is '["e",{"pluribus":"unum"}]' 91 | 92 | 93 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 94 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 95 | 96 | text = JSON.stringify([new Date()], function (key, value) { 97 | return this[key] instanceof Date ? 98 | 'Date(' + this[key] + ')' : value; 99 | }); 100 | // text is '["Date(---current time---)"]' 101 | 102 | 103 | JSON.parse(text, reviver) 104 | This method parses a JSON text to produce an object or array. 105 | It can throw a SyntaxError exception. 106 | 107 | The optional reviver parameter is a function that can filter and 108 | transform the results. It receives each of the keys and values, 109 | and its return value is used instead of the original value. 110 | If it returns what it received, then the structure is not modified. 111 | If it returns undefined then the member is deleted. 112 | 113 | Example: 114 | 115 | // Parse the text. Values that look like ISO date strings will 116 | // be converted to Date objects. 117 | 118 | myData = JSON.parse(text, function (key, value) { 119 | var a; 120 | if (typeof value === 'string') { 121 | a = 122 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 123 | if (a) { 124 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 125 | +a[5], +a[6])); 126 | } 127 | } 128 | return value; 129 | }); 130 | 131 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 132 | var d; 133 | if (typeof value === 'string' && 134 | value.slice(0, 5) === 'Date(' && 135 | value.slice(-1) === ')') { 136 | d = new Date(value.slice(5, -1)); 137 | if (d) { 138 | return d; 139 | } 140 | } 141 | return value; 142 | }); 143 | 144 | 145 | This is a reference implementation. You are free to copy, modify, or 146 | redistribute. 147 | */ 148 | 149 | /*jslint evil: true, strict: false */ 150 | 151 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 152 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 153 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 154 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 155 | test, toJSON, toString, valueOf 156 | */ 157 | 158 | 159 | // Create a JSON object only if one does not already exist. We create the 160 | // methods in a closure to avoid creating global variables. 161 | 162 | if (!this.JSON) { 163 | this.JSON = {}; 164 | } 165 | 166 | (function () { 167 | 168 | function f(n) { 169 | // Format integers to have at least two digits. 170 | return n < 10 ? '0' + n : n; 171 | } 172 | 173 | if (typeof Date.prototype.toJSON !== 'function') { 174 | 175 | Date.prototype.toJSON = function (key) { 176 | 177 | return isFinite(this.valueOf()) ? 178 | this.getUTCFullYear() + '-' + 179 | f(this.getUTCMonth() + 1) + '-' + 180 | f(this.getUTCDate()) + 'T' + 181 | f(this.getUTCHours()) + ':' + 182 | f(this.getUTCMinutes()) + ':' + 183 | f(this.getUTCSeconds()) + 'Z' : null; 184 | }; 185 | 186 | String.prototype.toJSON = 187 | Number.prototype.toJSON = 188 | Boolean.prototype.toJSON = function (key) { 189 | return this.valueOf(); 190 | }; 191 | } 192 | 193 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 194 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 195 | gap, 196 | indent, 197 | meta = { // table of character substitutions 198 | '\b': '\\b', 199 | '\t': '\\t', 200 | '\n': '\\n', 201 | '\f': '\\f', 202 | '\r': '\\r', 203 | '"' : '\\"', 204 | '\\': '\\\\' 205 | }, 206 | rep; 207 | 208 | 209 | function quote(string) { 210 | 211 | // If the string contains no control characters, no quote characters, and no 212 | // backslash characters, then we can safely slap some quotes around it. 213 | // Otherwise we must also replace the offending characters with safe escape 214 | // sequences. 215 | 216 | escapable.lastIndex = 0; 217 | return escapable.test(string) ? 218 | '"' + string.replace(escapable, function (a) { 219 | var c = meta[a]; 220 | return typeof c === 'string' ? c : 221 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 222 | }) + '"' : 223 | '"' + string + '"'; 224 | } 225 | 226 | 227 | function str(key, holder) { 228 | 229 | // Produce a string from holder[key]. 230 | 231 | var i, // The loop counter. 232 | k, // The member key. 233 | v, // The member value. 234 | length, 235 | mind = gap, 236 | partial, 237 | value = holder[key]; 238 | 239 | // If the value has a toJSON method, call it to obtain a replacement value. 240 | 241 | if (value && typeof value === 'object' && 242 | typeof value.toJSON === 'function') { 243 | value = value.toJSON(key); 244 | } 245 | 246 | // If we were called with a replacer function, then call the replacer to 247 | // obtain a replacement value. 248 | 249 | if (typeof rep === 'function') { 250 | value = rep.call(holder, key, value); 251 | } 252 | 253 | // What happens next depends on the value's type. 254 | 255 | switch (typeof value) { 256 | case 'string': 257 | return quote(value); 258 | 259 | case 'number': 260 | 261 | // JSON numbers must be finite. Encode non-finite numbers as null. 262 | 263 | return isFinite(value) ? String(value) : 'null'; 264 | 265 | case 'boolean': 266 | case 'null': 267 | 268 | // If the value is a boolean or null, convert it to a string. Note: 269 | // typeof null does not produce 'null'. The case is included here in 270 | // the remote chance that this gets fixed someday. 271 | 272 | return String(value); 273 | 274 | // If the type is 'object', we might be dealing with an object or an array or 275 | // null. 276 | 277 | case 'object': 278 | 279 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 280 | // so watch out for that case. 281 | 282 | if (!value) { 283 | return 'null'; 284 | } 285 | 286 | // Make an array to hold the partial results of stringifying this object value. 287 | 288 | gap += indent; 289 | partial = []; 290 | 291 | // Is the value an array? 292 | 293 | if (Object.prototype.toString.apply(value) === '[object Array]') { 294 | 295 | // The value is an array. Stringify every element. Use null as a placeholder 296 | // for non-JSON values. 297 | 298 | length = value.length; 299 | for (i = 0; i < length; i += 1) { 300 | partial[i] = str(i, value) || 'null'; 301 | } 302 | 303 | // Join all of the elements together, separated with commas, and wrap them in 304 | // brackets. 305 | 306 | v = partial.length === 0 ? '[]' : 307 | gap ? '[\n' + gap + 308 | partial.join(',\n' + gap) + '\n' + 309 | mind + ']' : 310 | '[' + partial.join(',') + ']'; 311 | gap = mind; 312 | return v; 313 | } 314 | 315 | // If the replacer is an array, use it to select the members to be stringified. 316 | 317 | if (rep && typeof rep === 'object') { 318 | length = rep.length; 319 | for (i = 0; i < length; i += 1) { 320 | k = rep[i]; 321 | if (typeof k === 'string') { 322 | v = str(k, value); 323 | if (v) { 324 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 325 | } 326 | } 327 | } 328 | } else { 329 | 330 | // Otherwise, iterate through all of the keys in the object. 331 | 332 | for (k in value) { 333 | if (Object.hasOwnProperty.call(value, k)) { 334 | v = str(k, value); 335 | if (v) { 336 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 337 | } 338 | } 339 | } 340 | } 341 | 342 | // Join all of the member texts together, separated with commas, 343 | // and wrap them in braces. 344 | 345 | v = partial.length === 0 ? '{}' : 346 | gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + 347 | mind + '}' : '{' + partial.join(',') + '}'; 348 | gap = mind; 349 | return v; 350 | } 351 | } 352 | 353 | // If the JSON object does not yet have a stringify method, give it one. 354 | 355 | if (typeof JSON.stringify !== 'function') { 356 | JSON.stringify = function (value, replacer, space) { 357 | 358 | // The stringify method takes a value and an optional replacer, and an optional 359 | // space parameter, and returns a JSON text. The replacer can be a function 360 | // that can replace values, or an array of strings that will select the keys. 361 | // A default replacer method can be provided. Use of the space parameter can 362 | // produce text that is more easily readable. 363 | 364 | var i; 365 | gap = ''; 366 | indent = ''; 367 | 368 | // If the space parameter is a number, make an indent string containing that 369 | // many spaces. 370 | 371 | if (typeof space === 'number') { 372 | for (i = 0; i < space; i += 1) { 373 | indent += ' '; 374 | } 375 | 376 | // If the space parameter is a string, it will be used as the indent string. 377 | 378 | } else if (typeof space === 'string') { 379 | indent = space; 380 | } 381 | 382 | // If there is a replacer, it must be a function or an array. 383 | // Otherwise, throw an error. 384 | 385 | rep = replacer; 386 | if (replacer && typeof replacer !== 'function' && 387 | (typeof replacer !== 'object' || 388 | typeof replacer.length !== 'number')) { 389 | throw new Error('JSON.stringify'); 390 | } 391 | 392 | // Make a fake root object containing our value under the key of ''. 393 | // Return the result of stringifying the value. 394 | 395 | return str('', {'': value}); 396 | }; 397 | } 398 | 399 | 400 | // If the JSON object does not yet have a parse method, give it one. 401 | 402 | if (typeof JSON.parse !== 'function') { 403 | JSON.parse = function (text, reviver) { 404 | 405 | // The parse method takes a text and an optional reviver function, and returns 406 | // a JavaScript value if the text is a valid JSON text. 407 | 408 | var j; 409 | 410 | function walk(holder, key) { 411 | 412 | // The walk method is used to recursively walk the resulting structure so 413 | // that modifications can be made. 414 | 415 | var k, v, value = holder[key]; 416 | if (value && typeof value === 'object') { 417 | for (k in value) { 418 | if (Object.hasOwnProperty.call(value, k)) { 419 | v = walk(value, k); 420 | if (v !== undefined) { 421 | value[k] = v; 422 | } else { 423 | delete value[k]; 424 | } 425 | } 426 | } 427 | } 428 | return reviver.call(holder, key, value); 429 | } 430 | 431 | 432 | // Parsing happens in four stages. In the first stage, we replace certain 433 | // Unicode characters with escape sequences. JavaScript handles many characters 434 | // incorrectly, either silently deleting them, or treating them as line endings. 435 | 436 | text = String(text); 437 | cx.lastIndex = 0; 438 | if (cx.test(text)) { 439 | text = text.replace(cx, function (a) { 440 | return '\\u' + 441 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 442 | }); 443 | } 444 | 445 | // In the second stage, we run the text against regular expressions that look 446 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 447 | // because they can cause invocation, and '=' because it can cause mutation. 448 | // But just to be safe, we want to reject all unexpected forms. 449 | 450 | // We split the second stage into 4 regexp operations in order to work around 451 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 452 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 453 | // replace all simple value tokens with ']' characters. Third, we delete all 454 | // open brackets that follow a colon or comma or that begin the text. Finally, 455 | // we look to see that the remaining characters are only whitespace or ']' or 456 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 457 | 458 | if (/^[\],:{}\s]*$/. 459 | test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). 460 | replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). 461 | replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 462 | 463 | // In the third stage we use the eval function to compile the text into a 464 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 465 | // in JavaScript: it can begin a block or an object literal. We wrap the text 466 | // in parens to eliminate the ambiguity. 467 | 468 | j = eval('(' + text + ')'); 469 | 470 | // In the optional fourth stage, we recursively walk the new structure, passing 471 | // each name/value pair to a reviver function for possible transformation. 472 | 473 | return typeof reviver === 'function' ? 474 | walk({'': j}, '') : j; 475 | } 476 | 477 | // If the text is not JSON parseable, then a SyntaxError is thrown. 478 | 479 | throw new SyntaxError('JSON.parse'); 480 | }; 481 | } 482 | }()); 483 | -------------------------------------------------------------------------------- /lib/user-image-cache/user-image-cache.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2011 Kevin Decker (http://www.incaseofstairs.com/) 3 | * See LICENSE for license information 4 | */ 5 | // Specs in use: 6 | // http://www.w3.org/TR/FileAPI/ 7 | // http://www.w3.org/TR/webstorage/ 8 | var UserImageCache; 9 | (function() { 10 | var curEntry, 11 | remoteProxyUrl, 12 | image; 13 | 14 | // Check for browser support of session storage and that it is accessible 15 | // This may be inaccessible under certain contexts such as file:// 16 | function supportsSessionStorage() { 17 | try { 18 | return !!window.sessionStorage; 19 | } catch (err) { 20 | return false; 21 | } 22 | } 23 | var localDataBinding = (function() { 24 | if (supportsSessionStorage()) { 25 | // If they support FileReader they really should support storage... but who knows (With the exception of file://) 26 | return { 27 | count: function() { 28 | return parseInt(sessionStorage.getItem("imageList-count"), 10)||0; 29 | }, 30 | lru: function(set) { 31 | if (set) { 32 | sessionStorage.setItem("imageList-lru", set.join(",")); 33 | } else { 34 | var lru = sessionStorage.getItem("imageList-lru")||""; 35 | if (!lru) { 36 | return []; 37 | } else { 38 | return lru.split(","); 39 | } 40 | } 41 | }, 42 | reset: function() { 43 | var len = this.count()+1; 44 | while (--len) { 45 | sessionStorage.removeItem("imageList-src-" + len); 46 | sessionStorage.removeItem("imageList-display-" + len); 47 | } 48 | sessionStorage.removeItem("imageList-lru"); 49 | sessionStorage.removeItem("imageList-count"); 50 | }, 51 | storeImage: function(name, data) { 52 | var count = this.count(), 53 | entryId = count+1, 54 | lru; 55 | 56 | do { 57 | try { 58 | sessionStorage.setItem("imageList-src-" + entryId, data); 59 | sessionStorage.setItem("imageList-display-" + entryId, name); 60 | 61 | lru = this.lru(); 62 | lru.push(entryId); 63 | this.lru(lru); 64 | 65 | break; 66 | } catch (err) { 67 | // Cache filled, remove the least recently used 68 | lru = this.lru(); 69 | sessionStorage.removeItem("imageList-src-" + lru[0]); 70 | sessionStorage.removeItem("imageList-display-" + lru[0]); 71 | lru.shift(); 72 | this.lru(lru); 73 | 74 | // Also cleanup any data that may have made it in 75 | sessionStorage.removeItem("imageList-src-" + entryId); 76 | sessionStorage.removeItem("imageList-display-" + entryId); 77 | 78 | count--; 79 | } 80 | } while (count > 0); 81 | 82 | sessionStorage.setItem("imageList-count", entryId); 83 | return entryId; 84 | }, 85 | getImage: function(entryId) { 86 | var ret = { 87 | src: sessionStorage.getItem("imageList-src-" + entryId), 88 | displayName: sessionStorage.getItem("imageList-display-" + entryId) 89 | }; 90 | 91 | var lru = this.lru().filter(function(a) { 92 | return a != entryId; 93 | }); 94 | if (ret.src) { 95 | lru.push(entryId); 96 | } 97 | this.lru(lru); 98 | 99 | return ret; 100 | } 101 | }; 102 | } else { 103 | // Fail over to plain js structures, meaing that refresh, etc will cause failures. 104 | var cache = []; 105 | return { 106 | reset: function() { 107 | cache = []; 108 | }, 109 | storeImage: function(name, data) { 110 | cache.push({ src: data, displayName: name }); 111 | return cache.length; 112 | }, 113 | getImage: function(entryId) { 114 | return cache[entryId-1]; 115 | } 116 | }; 117 | } 118 | })(); 119 | 120 | UserImageCache = { 121 | NOT_FOUND: "not_found", 122 | UNKNOWN_TYPE: "unknown_type", 123 | 124 | /** 125 | * Determines if local file reads are possible in the current execution environment. 126 | */ 127 | isLocalSupported: function() { 128 | try { 129 | return !!window.FileReader; 130 | } catch (err) { 131 | return false; 132 | } 133 | }, 134 | 135 | /** 136 | * Retrieves the entry id for the current entry, if one is defined. 137 | * This value may be passed to the load method to reload the image 138 | * if it is still cached. 139 | */ 140 | getEntryId: function() { return curEntry && curEntry.entryId; }, 141 | 142 | /** 143 | * Retrieves the display name for the current entry, if one is defined. 144 | */ 145 | getDisplayName: function() { return curEntry && curEntry.displayName; }, 146 | 147 | /** 148 | * Retrieves the src URI for the current entry, if one is defined. 149 | */ 150 | getSrc: function() { return curEntry && curEntry.src; }, 151 | 152 | /** 153 | * Sets the element that images will be loaded into. 154 | */ 155 | setImageEl: function(el) { 156 | image = el; 157 | }, 158 | 159 | /* 160 | * Sets the URL of the proxy server for loading remote URLs. On load the 161 | * file href URL will be appended to the remote proxy url, if defined. 162 | */ 163 | setRemoteProxy: function(proxyUrl) { 164 | remoteProxyUrl = proxyUrl; 165 | }, 166 | 167 | /** 168 | * Loads a given image. 169 | * 170 | * @param file may be one of: 171 | * - File object (if supported) 172 | * - Image URI 173 | * - Entry ID returned by getEntryId for a previous image 174 | * 175 | * @param onError(error) optional callback that is executed if the image can not be loaded 176 | * Errors include: 177 | * - UserImageCache.NOT_FOUND : Unable to lookup cached image element. 178 | * - UserImageCache.UNKNOWN_TYPE : File is unknown type 179 | * - Result of FileReader.error 180 | */ 181 | load: function(file, onError) { 182 | if (!image) { 183 | throw new Error("Must call setImageEl prior to attempting to load an image"); 184 | } 185 | 186 | // the file from the session store if that is the case 187 | if (typeof file === "string") { 188 | var match = /^page-store:\/\/(.*)$/.exec(file); 189 | if (this.isLocalSupported() && match) { 190 | var loadEntry = localDataBinding.getImage(match[1]); 191 | if (!loadEntry || !loadEntry.src) { 192 | // We could not find the cache data. This could be due to a refresh in the local case, 193 | // or due to someone attempting to paste a URL that uses a local reference. 194 | onError && onError(UserImageCache.NOT_FOUND); 195 | return; 196 | } 197 | loadEntry.entryId = "page-store://" + match[1]; 198 | curEntry = loadEntry; 199 | } else { 200 | var srcUrl = file; 201 | if (remoteProxyUrl && /https?:\/\/.*/.test(file)) { 202 | srcUrl = remoteProxyUrl + encodeURIComponent(file); 203 | } 204 | curEntry = { entryId: file, src: srcUrl, displayName: file }; 205 | } 206 | image.src = UserImageCache.getSrc(); 207 | } else if (this.isLocalSupported() && file instanceof File) { 208 | var reader = new FileReader(); 209 | reader.onload = function(event) { 210 | var entryId = localDataBinding.storeImage(file.name || file.fileName, reader.result); // std || impl to be safe 211 | curEntry = localDataBinding.getImage(entryId); 212 | if (!curEntry || !curEntry.src) { 213 | // The file is too large for the remaining data 214 | curEntry = {src: reader.result, displayName: file.name || file.fileName}; 215 | } 216 | curEntry.entryId = "page-store://" + entryId; 217 | image.src = UserImageCache.getSrc(); 218 | }; 219 | reader.onerror = function(event) { 220 | onError && onError(reader.error); 221 | }; 222 | reader.readAsDataURL(file); 223 | } else { 224 | onError && onError(UserImageCache.UNKNOWN_TYPE); 225 | } 226 | }, 227 | 228 | reset: function() { 229 | localDataBinding.reset(); 230 | image = undefined; 231 | curEntry = undefined; 232 | remoteProxyUrl = undefined; 233 | } 234 | }; 235 | })(); 236 | --------------------------------------------------------------------------------