| <!DOCTYPE html> |
| <!-- |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| --> |
| <div id="bodydiv" class="body"> |
| |
| <div id="viewcontent" class="viewcontent"> |
| <div id="pagecontainer"> |
| <div id="pagediv" class="pagediv"> |
| </div> |
| |
| <!-- |
| <div class="guide" style="position: absolute; left: 0px; top: 0px; width: 320px; height: 460px;"></div> |
| <div class="guide" style="position: absolute; left: 0px; top: 0px; width: 480px; height: 300px;"></div> |
| <div class="guide" style="position: absolute; left: 0px; top: 0px; width: 768px; height: 911px;"></div> |
| <div class="guide" style="position: absolute; left: 0px; top: 0px; width: 1024px; height: 655px;"></div> |
| --> |
| |
| </div> |
| |
| <div id="playdiv" class="playdiv" style="display: none;"></div> |
| |
| </div> |
| |
| <div id="palettecontainer"> |
| <div id="paletteview" style="display: none;"> |
| |
| <div id="palettecontent" class="palettecontent"> |
| <table class="palettetable"> |
| <tr><td class="palettetd"><span class="hd1" id="palette:h1"><span>Header 1</span></span></td></tr> |
| <tr><td class="palettetd"><span class="hd2" id="palette:h2"><span>Header 2</span></span></td></tr> |
| <tr><td class="palettetd"><span class="section" id="palette:section"><span>Section</span></span></td></tr> |
| <tr><td class="palettetd"><span class="text" id="palette:text"><span>Some text</span></span></td></tr> |
| <tr><td class="palettetd"><span class="link" id="palette:link"><a href="/"><span>Link</span></a></span></td></tr> |
| <tr><td class="palettetd"><span class="button" id="palette:graybutton"><input type="button" value="Button" class="graybutton"/></span></td></tr> |
| <tr><td class="palettetd"><span class="button" id="palette:redbutton"><input type="button" value="Button" class="redbutton"/></span></td></tr> |
| <tr><td class="palettetd"><span class="button" id="palette:greenbutton"><input type="button" value="Button" class="greenbutton"/></span></td></tr> |
| <tr><td class="palettetd"><span class="button" id="palette:bluebutton"><input type="button" value="Button" class="bluebutton"/></span></td></tr> |
| <tr><td class="palettetd"><span class="entry" id="palette:entry"><input type="text" value="Entry Field" class="flatentry" size="10" autocapitalize="off" readonly="true" style="cursor: default;"/></span></td></tr> |
| <tr><td class="palettetd"><span class="password" id="palette:password"><input type="password" value="Password" class="flatentry" size="10" readonly="true" style="cursor: default;"/></span></td></tr> |
| <tr><td class="palettetd"><span class="checkbox" id="palette:checkbox"><input type="checkbox" value="Checkbox" class="flatcheckbox"/><span>Checkbox</span></span></td></tr> |
| <!-- |
| <tr><td class="palettetd"><span class="select" id="palette:select"><select disabled="true"><option value="select">Selection</option></select></span></td></tr> |
| --> |
| <tr><td class="palettetd"><span class="list" id="palette:list"> |
| <table class="datatable"> |
| <tr><td class="datatd"><span>List</span></td></tr> |
| <tr><td class="datatd"><span>List</span></td></tr> |
| <tr><td class="datatd"><span>List</span></td></tr> |
| </table> |
| </span></td></tr> |
| <tr><td class="palettetd"><span class="table" id="palette:table"> |
| <table class="datatable"> |
| <tr><td class="datatdl"><span>Table</span></td><td class="datatdr"><span>Table</span></td></tr> |
| <tr><td class="datatdl"><span>Table</span></td><td class="datatdr"><span>Table</span></td></tr> |
| <tr><td class="datatdl"><span>Table</span></td><td class="datatdr"><span>Table</span></td></tr> |
| </table> |
| </span></td></tr> |
| <tr><td class="palettetd"><span class="img" id="palette:img"><img id="imgimg"/></span></td></tr> |
| </table> |
| </div> |
| |
| </div> |
| </div> |
| |
| <div id="xhtmlbuffer" style="display: none;"></div> |
| |
| <script type="text/javascript"> |
| (function pagebody() { |
| |
| /** |
| * Get the current app name. |
| */ |
| var appname = ui.fragmentParams(location)['app']; |
| |
| /** |
| * Setup page layout. |
| */ |
| (function layout() { |
| document.title = config.windowtitle() + ' - Page - ' + appname; |
| |
| $('viewhead').innerHTML = '<span id="appTitle" class="cmenu">' + appname + '</span>' + |
| '<input type="button" id="deleteWidgetButton" title="Delete a widget" class="redbutton plusminus" style="position: absolute; top: 4px; left: 2px;" disabled="true" value="-"/>' + |
| '<span style="position: absolute; top: 0px; left: 37px; right: 110px; padding: 0px; background: transparent;"><input id="widgetValue" type="text" value="" class="flatentry" title="Widget value" autocapitalize="off" placeholder="Value" style="position: absolute; left: 0px; top: 4px; width: 100%; display: none;" readonly="readonly"/></span>' + |
| '<input type="button" id="playPageButton" title="View page" class="greenbutton plusminus" style="position: absolute; top: 4px; right: 72px;" value=">"/>' + |
| '<input type="button" id="copyWidgetButton" title="Copy a widget" class="bluebutton" style="position: absolute; top: 4px; right: 37px; font-size: 16px;" disabled="true" value="C"/>' + |
| '<input type="button" id="addWidgetButton" title="Add a widget" class="bluebutton plusminus" style="position: absolute; top: 4px; right: 2px;" disabled="true" value="+"/>'; |
| //'<input type="button" id="appInfoButton" title="View app info" class="bluebutton" style="position: absolute; top: 4px; right: 2px; font-size: 16px;" value="i"/>'; |
| |
| if (ui.isMobile()) { |
| $('palettecontainer').className = 'palettecontainer3dm'; |
| $('paletteview').className = 'paletteloaded3dm'; |
| } else { |
| $('viewcontent').className = 'viewcontent flatscrollbars'; |
| $('palettecontainer').className = 'palettecontainer3d'; |
| $('paletteview').className = 'paletteloaded3d'; |
| $('palettecontent').className = 'palettecontent flatscrollbars'; |
| } |
| |
| $('imgimg').src = ui.b64png(appcache.get('/public/img.b64')); |
| })(); |
| |
| /** |
| * Track the current page, author, and saved XHTML content. |
| */ |
| var author = ''; |
| var editable = false; |
| var savedxhtml = ''; |
| |
| /** |
| * Initialize component references. |
| */ |
| var editorComp = sca.component('Editor'); |
| var pages = sca.reference(editorComp, 'pages'); |
| |
| /** |
| * Return the transform property of a widget. |
| */ |
| var msiefixupbounds = ui.isMSIE(); |
| function widgettransform(e) { |
| if (!isNull(e.xtranslate)) |
| return [e.xtranslate, e.ytranslate]; |
| var t = e.style.getPropertyValue('-webkit-transform') || e.style.getPropertyValue('-moz-transform') || |
| e.style.getPropertyValue('-ms-transform') || e.style.getPropertyValue('-o-transform') || |
| e.style.getPropertyValue('transform'); |
| if (t) { |
| var xy = t.split('(')[1].split(')')[0].split(','); |
| return [ui.numpos(xy[0]), ui.numpos(xy[1])]; |
| } |
| if (e.id.substring(0, 8) == 'palette:') { |
| // On Internet Explorer get the view bounding rect as the palette |
| // doesn't return a correct bounding rect |
| var pbr = msiefixupbounds? $('viewcontent').getBoundingClientRect() : $('palettecontent').getBoundingClientRect(); |
| var br = e.getBoundingClientRect(); |
| return [br.left - pbr.left, br.top - pbr.top]; |
| } |
| return [0, 0]; |
| } |
| |
| /** |
| * Return the x position of a widget. |
| */ |
| function widgetxpos(e) { |
| var t = widgettransform(e)[0]; |
| return ui.numpos(e.style.left) + (isNull(t)? 0 : t); |
| } |
| |
| /** |
| * Return the y position of a widget. |
| */ |
| function widgetypos(e) { |
| var t = widgettransform(e)[1]; |
| return ui.numpos(e.style.top) + (isNull(t)? 0 : t); |
| } |
| |
| /** |
| * Return the class of a widget. |
| */ |
| function widgetclass(e) { |
| return e.className.split(' ')[0]; |
| } |
| |
| /** |
| * Initialize a widget. |
| */ |
| function fixupwidget(e) { |
| |
| // Add draggable class |
| var wc = e.className; |
| e.className = ui.isMobile()? (wc + ' draggable3dm') : (wc + ' draggable3d'); |
| |
| // Convert widget position to a CSS transform |
| var x = ui.numpos(e.style.left); |
| var y = ui.numpos(e.style.top); |
| var t = 'translate(' + x + 'px,' + y + 'px)'; |
| e.style.setProperty('-webkit-transform', t, null); |
| e.style.setProperty('-moz-transform', t, null); |
| e.style.setProperty('-o-transform', t, null); |
| e.style.setProperty('-ms-transform', t, null); |
| e.style.setProperty('transform', t, null); |
| e.xtranslate = x; |
| e.ytranslate = y; |
| e.style.left = ui.pixpos(0); |
| e.style.top = ui.pixpos(0); |
| |
| if (wc == 'entry' || wc == 'password') { |
| var i = car(childElements(e)); |
| i.readOnly = true; |
| i.style.cursor = 'default'; |
| return e; |
| } |
| if (wc == 'link') { |
| var l = car(childElements(e)); |
| l.onclick = function(e) { return false; }; |
| return e; |
| } |
| return e; |
| } |
| |
| /** |
| * Cleanup a widget before saving it. |
| */ |
| function cleanupwidget(e) { |
| //debug('cleanupwidget', e); |
| |
| // Adjust widget class |
| var wc = widgetclass(e); |
| e.className = wc; |
| |
| // Convert CSS transform to an absolute position |
| e.style.left = ui.pixpos(widgetxpos(e)); |
| e.style.top = ui.pixpos(widgetypos(e)); |
| e.style.removeProperty('-webkit-transform'); |
| e.style.removeProperty('-moz-transform'); |
| e.style.removeProperty('-o-transform'); |
| e.style.removeProperty('-ms-transform'); |
| e.style.removeProperty('transform'); |
| e.xtranslate = null; |
| e.ytranslate = null; |
| |
| // Clear outline |
| e.style.removeProperty('outline'); |
| |
| if (wc == 'entry' || wc == 'password') { |
| var i = car(childElements(e)); |
| i.readOnly = false; |
| i.style.cursor = null; |
| return e; |
| } |
| if (wc == 'link') { |
| var l = car(childElements(e)); |
| l.onclick = null; |
| return e; |
| } |
| return e; |
| } |
| |
| /** |
| * Clone a widget. |
| */ |
| function clonewidget(e) { |
| |
| /** |
| * Clone an element's HTML. |
| */ |
| function mkclone(e) { |
| var ne = document.createElement('span'); |
| |
| // Skip the palette: prefix |
| ne.id = 'page:' + e.id.substr(8); |
| |
| // Copy the class and HTML content |
| ne.className = widgetclass(e); |
| ne.innerHTML = e.innerHTML; |
| |
| // Fixup the widget |
| fixupwidget(ne); |
| |
| return ne; |
| } |
| |
| /** |
| * Clone an element's position. |
| */ |
| function posclone(ne, e) { |
| ne.style.position = 'absolute'; |
| movewidget(ne, widgetxpos(e), widgetypos(e)); |
| return ne; |
| } |
| |
| return posclone(mkclone(e), e); |
| } |
| |
| /** |
| * Select a widget. |
| */ |
| function selectwidget(n, s) { |
| //debug('selectwidget', n, s); |
| if (isNull(n) || !s) { |
| // Clear the widget value field |
| $('widgetValue').value = ''; |
| $('widgetValue').readOnly = true; |
| $('widgetValue').style.display = 'none'; |
| |
| // Show the app title |
| $('appTitle').style.display = 'block'; |
| |
| // Update the copy and delete buttons |
| $('copyWidgetButton').disabled = true; |
| $('deleteWidgetButton').disabled = true; |
| |
| // Clear the widget outline |
| if (!isNull(n)) |
| n.style.removeProperty('outline'); |
| return true; |
| } |
| |
| // Outline the widget |
| n.style.outline = '2px solid #598edd'; |
| |
| // Update the widget value field |
| $('widgetValue').value = widgettext(n); |
| $('widgetValue').readOnly = false || !editable; |
| $('widgetValue').style.display = 'block'; |
| |
| // Hide the app title |
| $('appTitle').style.display = 'none'; |
| |
| // Update the copy and delete buttons |
| $('copyWidgetButton').disabled = false || !editable; |
| $('deleteWidgetButton').disabled = false || !editable; |
| return true; |
| } |
| |
| /** |
| * Return the text of a widget. |
| */ |
| function widgettext(e) { |
| function formula(e) { |
| var f = e.id; |
| if (f.substring(0, 5) != 'page:') |
| return '=' + f; |
| return ''; |
| } |
| |
| function constant(e, f) { |
| var wc = widgetclass(e); |
| if (wc == 'hd1' || wc == 'hd2' || wc == 'text' || wc == 'section') { |
| var t = car(childElements(e)).innerHTML; |
| return t == f? '' : t; |
| } |
| if (wc == 'button' || wc == 'checkbox') { |
| var t = car(childElements(e)).value; |
| return t == f? '' : t; |
| } |
| if (wc == 'entry' || wc == 'password') { |
| var t = car(childElements(e)).defaultValue; |
| return t == f? '' : t; |
| } |
| if (wc == 'select') { |
| var t = car(childElements(car(childElements(e)))).value; |
| return t == f? '' : t; |
| } |
| if (wc == 'link') { |
| var lhr = car(childElements(e)).href; |
| var hr = lhr.substring(0, 5) == 'link:'? lhr.substring(5) : ''; |
| var t = car(childElements(car(childElements(e)))).innerHTML; |
| return t == f? hr : (t == hr? hr : (t == ''? hr : hr + ',' + t)); |
| } |
| if (wc == 'img') { |
| var src = car(childElements(e)).src; |
| return src == location.href? '' : src; |
| } |
| if (wc == 'list') |
| return ''; |
| if (wc == 'table') |
| return ''; |
| return ''; |
| } |
| |
| var f = formula(e); |
| var c = constant(e, f); |
| return f == ''? c : (c == ''? f : f + ',' + c); |
| } |
| |
| /** |
| * Return true if a widget has editable text. |
| */ |
| function widgethastext(e) { |
| var wc = widgetclass(e); |
| if (wc == 'hd1' || wc == 'hd2' || wc == 'text' || wc == 'section') |
| return true; |
| if (wc == 'button' || wc == 'checkbox') |
| return true; |
| if (wc == 'entry' || wc == 'password') |
| return true; |
| if (wc == 'select') |
| return false; |
| if (wc == 'link') |
| return true; |
| if (wc == 'img') |
| return true; |
| if (wc == 'list') |
| return false; |
| if (wc == 'table') |
| return false; |
| return false; |
| } |
| |
| /** |
| * Set the text of a widget. |
| */ |
| function setwidgettext(e, t) { |
| function formula(t) { |
| if (t.length > 1 && t.substring(0, 1) == '=') |
| return car(t.split(',')); |
| return ''; |
| } |
| |
| function constant(t) { |
| return t.length > 1 && t.substring(0, 1) == '='? cdr(t.split(',')) : t.split(','); |
| } |
| |
| var f = formula(t); |
| var c = constant(t); |
| |
| var wc = widgetclass(e); |
| e.id = f != ''? f.substring(1) : ('page:' + wc); |
| |
| if (wc == 'hd1' || wc == 'hd2' || wc == 'text' || wc == 'section') { |
| car(childElements(e)).innerHTML = isNull(c)? f : car(c); |
| return t; |
| } |
| if (wc == 'button') { |
| car(childElements(e)).value = isNull(c)? f : car(c); |
| return t; |
| } |
| if (wc == 'entry' || wc == 'password') { |
| car(childElements(e)).defaultValue = isNull(c)? f : car(c); |
| return t; |
| } |
| if (wc == 'checkbox') { |
| car(childElements(e)).value = isNull(c)? f : car(c); |
| map(function(n) { if (n.nodeName == "SPAN") n.innerHTML = isNull(c)? f : car(c); return n; }, nodeList(e.childNodes)); |
| return t; |
| } |
| if (wc == 'select') { |
| var ce = car(childElements(car(childElements(e)))); |
| ce.value = isNull(c)? f : car(c); |
| ce.innerHTML = isNull(c)? f : car(c); |
| return t; |
| } |
| if (wc == 'list') { |
| e.innerHTML = '<table class="datatable" style="width: 100%;;"><tr><td class="datatd">' + (isNull(c)? f : car(c)) + '</td></tr><tr><td class="datatd">...</td></tr></table>'; |
| return t; |
| } |
| if (wc == 'table') { |
| e.innerHTML = '<table class="datatable" style="width: 100%;"><tr><td class="datatdl">' + (isNull(c)? f : car(c)) + '</td><td class="datatdr">...</td></tr><tr><td class="datatdl">...</td><td class="datatdr">...</td></tr></table>'; |
| return t; |
| } |
| if (wc == 'link') { |
| var ce = car(childElements(e)); |
| ce.href = isNull(c)? 'link:/index.html' : ('link:' + car(c)); |
| car(childElements(ce)).innerHTML = isNull(c)? (f != ''? f : '/index.html') : isNull(cdr(c))? (f != ''? f : car(c)) : cadr(c); |
| return t; |
| } |
| if (wc == 'img') { |
| car(childElements(e)).src = isNull(c)? '/public/img.png' : car(c); |
| return t; |
| } |
| return ''; |
| } |
| |
| /** |
| * Align a pos along a 9pixel grid. |
| */ |
| function snaptogrid(x) { |
| return Math.round(x / 10) * 10; |
| } |
| |
| /** |
| * Bring a node to the top. |
| */ |
| function bringtotop(n) { |
| n.parentNode.appendChild(n); |
| } |
| |
| /** |
| * Move a widget. |
| */ |
| var iefixuptransform = ui.isMSIE(); |
| var fffixupoutline = ui.isFirefox() && (ui.firefoxVersion() > 4); |
| function movewidget(e, x, y) { |
| var t = 'translate(' + x + 'px,' + y + 'px)'; |
| e.style.setProperty('-webkit-transform', t, null); |
| e.style.setProperty('-moz-transform', t, null); |
| e.style.setProperty('-o-transform', t, null); |
| // On Internet Explorer set the property directly as setProperty |
| // doesn't seem to apply |
| if (iefixuptransform) |
| e.style.msTransform = t; |
| e.style.setProperty('transform', t, null); |
| e.xtranslate = x; |
| e.ytranslate = y; |
| return e; |
| } |
| |
| /** |
| * Return a widget bounding rect. |
| */ |
| var fffixupbounds = ui.isFirefox() && (ui.firefoxVersion() < 12); |
| function widgetbounds(e) { |
| var br = e.getBoundingClientRect(); |
| if (!fffixupbounds) |
| return br; |
| |
| // On Firefox < 12, apply CSS transform translation to bounding rect manually |
| //debug('fixup br', e, br.left, br.top, br.right, br.bottom, t[0], t[1]); |
| function fixuptransform(e) { |
| var t = widgettransform(e); |
| if (!isNull(e.xtranslate)) |
| return [e.xtranslate, e.ytranslate]; |
| var t = e.style.getPropertyValue('-webkit-transform') || e.style.getPropertyValue('-moz-transform') || |
| e.style.getPropertyValue('-ms-transform') || e.style.getPropertyValue('-o-transform') || |
| e.style.getPropertyValue('transform'); |
| if (t) { |
| var xy = t.split('(')[1].split(')')[0].split(','); |
| return [ui.numpos(xy[0]), ui.numpos(xy[1])]; |
| } |
| return [0, 0]; |
| } |
| |
| var t = fixuptransform(e); |
| var fbr = new Object(); |
| fbr.left = br.left + t[0]; |
| fbr.top = br.top + t[1]; |
| fbr.right = fbr.left + e.offsetWidth; |
| fbr.bottom = fbr.top + e.offsetHeight; |
| return fbr; |
| } |
| |
| /** |
| * Find a draggable element in a list. |
| */ |
| function draggable(x, y, l) { |
| //debug('draggable?', x, y, l); |
| if (isNull(l)) |
| return null; |
| var n = car(l); |
| if (isNull(n.id) || n.id == '') { |
| var d = draggable(x, y, reverse(nodeList(n.childNodes))); |
| if (!isNull(d)) |
| return d; |
| return draggable(x, y, cdr(l)); |
| } |
| var br = widgetbounds(n); |
| //debug('element br', n, br.left, br.top, br.right, br.bottom); |
| if (x >= br.left && x <= br.right && y >= br.top && y <= br.bottom) |
| return n; |
| return draggable(x, y, cdr(l)); |
| } |
| |
| /** |
| * Play page in a frame. |
| */ |
| function showplaying() { |
| $('playPageButton').value = '<'; |
| $('playdiv').style.display = 'block'; |
| $('playdiv').visible = true; |
| $('playdiv').innerHTML = ''; |
| $('playdiv').innerHTML = '<iframe id="playappframe" style="position: relative; border: 0px;" scrolling="no" frameborder="0" src="/' + appname + '"></iframe>'; |
| if ($('pagediv').visible) { |
| $('pagediv').style.display = 'none' |
| $('pagediv').visible = false; |
| } |
| hidepalette(); |
| return true; |
| } |
| |
| /** |
| * Show the page editor. |
| */ |
| function showeditor() { |
| $('playPageButton').value = '>'; |
| $('pagediv').style.display = 'block' |
| $('pagediv').visible = true; |
| if ($('playdiv').visible) { |
| $('playdiv').style.display = 'none'; |
| $('playdiv').innerHTML = ''; |
| $('playdiv').visible = false; |
| } |
| hidepalette(); |
| return true; |
| } |
| |
| /** |
| * Palette animation. |
| */ |
| function palettetransitionend(e) { |
| if ($('paletteview').className == 'paletteunloaded3dm') |
| $('paletteview').style.display = 'none'; |
| } |
| |
| $('paletteview').addEventListener('webkitTransitionEnd', palettetransitionend, false); |
| $('paletteview').addEventListener('transitionend', palettetransitionend, false); |
| |
| /** |
| * Show the palette. |
| */ |
| function showpalette() { |
| if (ui.isMobile()) { |
| $('paletteview').className = 'paletteloading3dm'; |
| $('paletteview').style.display = 'block'; |
| $('paletteview').visible = true; |
| ui.delay(function transitionview() { |
| $('paletteview').className = 'paletteloaded3dm'; |
| }); |
| } else { |
| $('paletteview').className = 'paletteloaded3d'; |
| $('paletteview').style.display = 'block'; |
| $('paletteview').visible = true; |
| } |
| return true; |
| } |
| |
| /** |
| * Hide the palette. |
| */ |
| function hidepalette() { |
| if (ui.isMobile()) { |
| $('paletteview').className = 'paletteunloading3dm'; |
| $('paletteview').visible = false; |
| ui.delay(function transitionview() { |
| $('paletteview').className = 'paletteunloaded3dm'; |
| }); |
| } else { |
| $('paletteview').className = 'paletteunloaded3d'; |
| $('paletteview').style.display = 'none'; |
| $('paletteview').visible = false; |
| } |
| return true; |
| } |
| |
| /** |
| * Create page editor. |
| */ |
| function mkeditor() { |
| |
| // Initialize header elements |
| $('widgetValue').readOnly = true; |
| $('widgetValue').style.display = 'none'; |
| $('appTitle').style.display = 'block'; |
| $('copyWidgetButton').disabled = true; |
| $('deleteWidgetButton').disabled = true; |
| $('addWidgetButton').disabled = !editable; |
| |
| // Track widget dragging and selection |
| var dragging = null; |
| var selected = null; |
| var moved = false; |
| var mdown = false; |
| var moveX = 0; |
| var moveY = 0; |
| var dragX = 0; |
| var dragY = 0; |
| |
| /** |
| * Handle a page change event |
| */ |
| function onpagechange(prop) { |
| if (!editable) |
| return false; |
| |
| var newxml = pagexhtml(); |
| if (savedxhtml == newxml) |
| return false; |
| showstatus('Modified'); |
| |
| // Save property changes right away |
| if (prop) |
| return save(newxml); |
| |
| // Autosave other changes after 1 second |
| ui.async(function autosave() { |
| if (savedxhtml == newxml) { |
| showstatus('Saved'); |
| return false; |
| } |
| return save(newxml); |
| }, 1000); |
| return true; |
| } |
| |
| /** |
| * Handle a widget select event. |
| */ |
| function onselectwidget(w) { |
| if (w == selected) |
| return true; |
| selected = w; |
| return true; |
| } |
| |
| /** |
| * Render widget move animation. |
| */ |
| function onmoveanimation() { |
| //debug('onmoveanimation'); |
| |
| // Stop animation if we're not dragging an element anymore |
| if (dragging == null) |
| return true; |
| |
| // Request the next animation frame |
| ui.animation(onmoveanimation); |
| |
| // Nothing to do if the selected widget has not moved |
| if (moveX == dragX && moveY == dragY) |
| return true; |
| |
| // Compute position of dragged element |
| var curX = widgetxpos(dragging); |
| var curY = widgetypos(dragging); |
| var newX = curX + (moveX - dragX); |
| var newY = curY + (moveY - dragY); |
| |
| var okx = true; |
| if (newX + dragging.clientWidth > 1024) { |
| newX = 1024 - dragging.clientWidth; |
| okx = false; |
| } |
| if (newX < 0) { |
| newX = 0; |
| okx = false; |
| } |
| if (okx) |
| dragX = moveX; |
| var oky = true; |
| if (newY + dragging.clientHeight > 1024) { |
| newY = 1024 - dragging.clientHeight; |
| oky= false; |
| } |
| if (newY < 0) { |
| newY = 0; |
| oky = false; |
| } |
| if (oky) |
| dragY = moveY; |
| |
| // On Firefox > 4, remove outline before moving widget as it's not |
| // correctly painted |
| if (fffixupoutline) |
| dragging.style.removeProperty('outline'); |
| |
| // Move the dragged element |
| movewidget(dragging, newX, newY); |
| |
| return true; |
| } |
| |
| /** |
| * Handle a mouse down event. |
| */ |
| function onmousedown(e) { |
| // On mouse controlled devices, run component selection logic right away |
| if (!ui.isMobile()) { |
| //debug('onmousedown-click'); |
| onclick(e); |
| } |
| |
| // Find a draggable widget |
| var d = draggable(moveX, moveY, reverse(nodeList($('pagediv').childNodes))); |
| //debug('dragging', d, 'selected', selected); |
| if (d == null || d != selected) |
| return true; |
| dragging = d; |
| |
| // Remember mouse position |
| dragX = moveX; |
| dragY = moveY; |
| |
| // Start move animation |
| ui.animation(onmoveanimation); |
| |
| e.preventDefault(); |
| return true; |
| } |
| |
| if (!ui.isMobile()) { |
| $('pagediv').onmousedown = function(e) { |
| //debug('onmousedown', e.target); |
| mdown = true; |
| moveX = e.clientX; |
| moveY = e.clientY; |
| moved = false; |
| return onmousedown(e); |
| }; |
| $('palettecontent').onmousedown = function(e) { |
| //debug('onmousedown', e.target); |
| mdown = true; |
| moveX = e.clientX; |
| moveY = e.clientY; |
| moved = false; |
| return onmousedown(e); |
| }; |
| } else { |
| $('pagediv').ontouchstart = function(e) { |
| //debug('ontouchstart'); |
| mdown = true; |
| moveX = e.touches[0].clientX; |
| moveY = e.touches[0].clientY; |
| moved = false; |
| return onmousedown(e); |
| }; |
| $('palettecontent').ontouchstart = function(e) { |
| //debug('ontouchstart'); |
| mdown = true; |
| moveX = e.touches[0].clientX; |
| moveY = e.touches[0].clientY; |
| moved = false; |
| return onmousedown(e); |
| }; |
| } |
| |
| /** |
| * Handle a mouse up event. |
| */ |
| function onmouseup(e) { |
| // Simulate onclick event |
| if (ui.isMobile() && !moved) { |
| //debug('ontouchend-click'); |
| return onclick(e); |
| } |
| |
| if (dragging == null) |
| return true; |
| |
| // Snap dragged widget to grid |
| var newX = snaptogrid(widgetxpos(dragging)); |
| var newY = snaptogrid(widgetypos(dragging)); |
| movewidget(dragging, newX, newY); |
| |
| // Forget dragged element |
| dragging = null; |
| |
| // Trigger page change event |
| onpagechange(false); |
| |
| // On Firefox > 4, re-apply the outline after the widget has been repositioned |
| if (fffixupoutline && !isNull(selected)) { |
| ui.delay(function() { |
| if (!isNull(selected)) |
| selected.style.outline = '2px solid #598edd'; |
| }, 32); |
| } |
| return true; |
| } |
| |
| if (!ui.isMobile()) { |
| window.onmouseup = function(e) { |
| //debug('onmouseup'); |
| if (!mdown) |
| return true; |
| return onmouseup(e); |
| }; |
| } else { |
| window.ontouchend = function(e) { |
| //debug('ontouchend'); |
| if (!mdown) |
| return true; |
| return onmouseup(e); |
| } |
| } |
| |
| if (!ui.isMobile()) { |
| window.onmousemove = function(e) { |
| //debug('onmousemove'); |
| |
| // Record mouse position |
| if (e.clientX != moveX) { |
| moved = true; |
| moveX = e.clientX; |
| } |
| if (e.clientY != moveY) { |
| moved = true; |
| moveY = e.clientY; |
| } |
| if (dragging == null) |
| return true; |
| return false; |
| }; |
| } else { |
| window.ontouchmove = function(e) { |
| //debug('ontouchmove'); |
| |
| // Record touch position |
| var t = e.touches[0]; |
| if (t.clientX != moveX) { |
| moved = true; |
| moveX = t.clientX; |
| } |
| if (t.clientY != moveY) { |
| moved = true; |
| moveY = t.clientY; |
| } |
| if (dragging == null) |
| return true; |
| return false; |
| }; |
| } |
| |
| /** |
| * Handle a mouse click event. |
| */ |
| function onclick(e) { |
| |
| // Find selected element |
| var palvis = $('paletteview').visible? true : false; |
| var s = draggable(moveX, moveY, reverse(nodeList((palvis? $('palettecontent') : $('pagediv')).childNodes))); |
| //debug('selected', s); |
| if (s == null) { |
| if (selected != null) { |
| |
| // Reset current selection |
| selectwidget(selected, false); |
| selected = null; |
| |
| // Trigger widget select event |
| onselectwidget(null); |
| } |
| |
| // Dismiss the palette |
| if (palvis && isNull(draggable(moveX, moveY, mklist($('palettecontent'))))) |
| hidepalette(); |
| |
| return true; |
| } |
| |
| // Deselect the previously selected element |
| selectwidget(selected, false); |
| |
| // Clone widget dragged from palette |
| if (s.id.substring(0, 8) == 'palette:') { |
| selected = clonewidget(s); |
| |
| // Add it to the page |
| $('pagediv').appendChild(selected); |
| movewidget(selected, widgetxpos(selected) + $('viewcontent').scrollLeft, widgetypos(selected) + $('viewcontent').scrollTop); |
| |
| // Hide the palette |
| hidepalette(); |
| |
| // Trigger page change event |
| onpagechange(true); |
| |
| // Select the element |
| selectwidget(selected, true); |
| |
| // Trigger widget select event |
| onselectwidget(selected); |
| |
| return true; |
| } |
| |
| // Bring selected widget to the top |
| selected = s; |
| bringtotop(selected); |
| |
| // Select the widget |
| selectwidget(selected, true); |
| |
| // Trigger widget select event |
| onselectwidget(selected); |
| |
| return true; |
| } |
| |
| /** |
| * Handle field on change events. |
| */ |
| $('widgetValue').onchange = $('widgetValue').onblur = function() { |
| if (selected == null) |
| return false; |
| setwidgettext(selected, $('widgetValue').value); |
| |
| // Trigger page change event |
| onpagechange(true); |
| return false; |
| }; |
| |
| // Handle add widget event. |
| ui.onclick($('addWidgetButton'), function(e) { |
| |
| // Show / hide the palette |
| if ($('paletteview').visible) |
| return hidepalette(); |
| return showpalette(); |
| }); |
| |
| // Handle delete event. |
| ui.onclick($('deleteWidgetButton'), function(e) { |
| if (selected == null) |
| return false; |
| |
| // Reset current selection |
| selectwidget(selected, false); |
| |
| // Remove selected widget |
| selected.parentNode.removeChild(selected); |
| selected = null; |
| |
| // Trigger widget select event |
| onselectwidget(null); |
| |
| // Trigger page change event |
| onpagechange(true); |
| return false; |
| }); |
| |
| // Handle copy event. |
| ui.onclick($('copyWidgetButton'), function(e) { |
| if (selected == null) |
| return false; |
| if (selected.id.substring(0, 8) == 'palette:') |
| return false; |
| |
| // Reset current selection |
| selectwidget(selected, false); |
| |
| // Clone selected widget |
| selected = clonewidget(selected); |
| |
| // Add it to the page |
| $('pagediv').appendChild(selected); |
| |
| // Move 10 pixels down right |
| movewidget(selected, widgetxpos(selected) + 10, widgetypos(selected) + 10); |
| |
| // Bring it to the top |
| bringtotop(selected); |
| |
| // Select the element |
| selectwidget(selected, true); |
| |
| // Trigger widget select event |
| onselectwidget(selected); |
| |
| // Trigger page change event |
| onpagechange(true); |
| return false; |
| }); |
| |
| /** |
| * Handle play page button event. |
| */ |
| ui.onclick($('playPageButton'), function(e) { |
| |
| // Show / hide the page play frame |
| if ($('playdiv').visible) |
| return showeditor(); |
| return showplaying(); |
| }); |
| |
| // Show the editor |
| showeditor(); |
| |
| return true; |
| }; |
| |
| /** |
| * Get and display the requested app page. |
| */ |
| (function getpage() { |
| if (isNull(appname)) |
| return false; |
| workingstatus(true); |
| showstatus('Loading'); |
| |
| return pages.get(appname, function(doc) { |
| |
| // Stop now if we didn't get a page |
| if (doc == null) { |
| errorstatus('Couldn\'t get the app info'); |
| workingstatus(false); |
| return false; |
| } |
| |
| // Get the page from the ATOM entry, convert it to XHTML and place it in a hidden buffer |
| var pageentry = car(atom.readATOMEntry(mklist(doc))); |
| var content = namedElementChild("'content", pageentry); |
| var el = isNull(content)? mklist() : elementChildren(content); |
| if (isNull(el)) |
| $('xhtmlbuffer').innerHTML = '<div id="page"></div>'; |
| else |
| $('xhtmlbuffer').innerHTML = writeStrings(writeXML(el, false)); |
| |
| // Remove any existing page nodes from the editor div |
| var fnodes = filter(function(e) { |
| if (isNull(e.id) || e.id == '') |
| return false; |
| return true; |
| }, nodeList($('pagediv').childNodes)); |
| |
| map(function(e) { |
| $('pagediv').removeChild(e); |
| }, fnodes); |
| |
| // Fixup widgets and append them to the editor |
| map(function(e) { |
| $('pagediv').appendChild(e); |
| fixupwidget(e); |
| return e; |
| }, nodeList($('xhtmlbuffer').childNodes[0].childNodes)); |
| |
| savedxhtml = pagexhtml($('pagediv')); |
| |
| // Enable author to edit the page |
| author = elementValue(namedElementChild("'author", pageentry)); |
| editable = author == username; |
| $('addWidgetButton').disabled = !editable; |
| if (editable) |
| onlinestatus(); |
| else |
| showstatus('Read only'); |
| |
| workingstatus(false); |
| return true; |
| }); |
| })(); |
| |
| /** |
| * Return the current page XHTML content. |
| */ |
| function pagexhtml() { |
| |
| // Copy page DOM to hidden buffer |
| $('xhtmlbuffer').innerHTML = '<div id="page"></div>' |
| var div = $('xhtmlbuffer').childNodes[0]; |
| |
| // Capture the nodes inside the page div |
| div.innerHTML = $('pagediv').innerHTML; |
| var nodes = nodeList(div.childNodes); |
| map(function(e) { |
| div.removeChild(e); |
| return e; |
| }, nodes); |
| |
| // Filter out palette and editor artifacts, which are not |
| // part of the page, as well as nodes positioned out the |
| // editing area |
| var fnodes = filter(function(e) { |
| if (isNull(e.id) || e.id == '') |
| return false; |
| return true; |
| }, nodes); |
| |
| // Reposition and cleanup nodes |
| map(function(e) { |
| cleanupwidget(e); |
| return e; |
| }, fnodes); |
| |
| // Sort them by position |
| var snodes = fnodes.sort(function(a, b) { |
| var ay = widgetypos(a); |
| var by = widgetypos(b); |
| if (ay < by) return -1; |
| if (ay > by) return 1; |
| var ax = widgetxpos(a); |
| var bx = widgetxpos(b); |
| if (ax < bx) return -1; |
| if (ax > bx) return 1; |
| return 0; |
| }); |
| |
| // Append the sorted nodes back to the div in order |
| map(function(e) { |
| div.appendChild(e); |
| return e; |
| }, snodes); |
| |
| // Convert the page to XHTML |
| var lxhtml = readXHTMLElement(div); |
| var xhtml = writeStrings(writeXML(lxhtml, false)); |
| return xhtml; |
| } |
| |
| /** |
| * Save the current page. |
| */ |
| function save(newxml) { |
| workingstatus(true); |
| showstatus('Saving'); |
| |
| // Get the current page XHTML content |
| savedxhtml = newxml; |
| |
| // Update the page ATOM entry |
| var entry = '<?xml version="1.0" encoding="UTF-8"?>\n' + '<entry xmlns="http://www.w3.org/2005/Atom">' + |
| '<title type="text">' + appname + '</title><id>' + appname + '</id><author><email>' + author + '</email></author>' + |
| '<content type="application/xml">' + newxml + '</content></entry>'; |
| |
| pages.put(appname, entry, function(e) { |
| if (e) { |
| showstatus('Local copy'); |
| workingstatus(false); |
| return false; |
| } |
| showstatus('Saved'); |
| workingstatus(false); |
| return false; |
| }); |
| return true; |
| }; |
| |
| /** |
| * Handle app info button event. |
| */ |
| /* Disabled for now. |
| ui.onclick($('appInfoButton'), function(e) { |
| return ui.navigate('/#view=info&app=' + appname, '_view'); |
| }); |
| */ |
| |
| /** |
| * Initialize the page editor. |
| */ |
| mkeditor(); |
| |
| })(); |
| </script> |
| |
| </div> |