From 900adda821e279a218de2836003afebb507d11cd Mon Sep 17 00:00:00 2001 From: Xuqing Kuang Date: Fri, 24 Aug 2012 14:57:53 +0800 Subject: [PATCH 01/38] [ADM] Fix bug: ID auto numbering occurring in reverse. --- src/js/adm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/adm.js b/src/js/adm.js index 364f82fa..d2d5e333 100644 --- a/src/js/adm.js +++ b/src/js/adm.js @@ -1487,7 +1487,7 @@ ADMNode.prototype.findNodesByProperty = function (propertyFilter) { // recurse on children children = this.getChildren(); - for (i = children.length - 1; i >= 0; i--) { + for (i = 0; i < children.length; i++) { result = result.concat(children[i].findNodesByProperty(propertyFilter)); } return result; From ad496c5a49d6bfe4c1c8cac65de86b9528a246a1 Mon Sep 17 00:00:00 2001 From: Xuqing Kuang Date: Fri, 17 Aug 2012 13:52:32 +0800 Subject: [PATCH 02/38] [Widgets] Added event properties to widgets. --- src/js/widgets.js | 178 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 176 insertions(+), 2 deletions(-) diff --git a/src/js/widgets.js b/src/js/widgets.js index 292e87f8..af28d813 100644 --- a/src/js/widgets.js +++ b/src/js/widgets.js @@ -215,6 +215,100 @@ var BWidgetRegistry = { type: "string", defaultValue: "", htmlAttribute: "id" + }, + + /* + * Event properties + */ + + // Touch events + tap: { + type: "event", + defaultValue: "", + visible: false, + }, + taphold: { + type: "event", + defaultValue: "", + visible: false, + }, + swipe: { + type: "event", + defaultValue: "", + visible: false, + }, + swipeleft: { + type: "event", + defaultValue: "", + visible: false, + }, + swiperight: { + type: "event", + defaultValue: "", + visible: false, + }, + + // Scroll events + scrollstart: { + type: "event", + defaultValue: "", + visible: false, + }, + scrollstop: { + type: "event", + defaultValue: "", + visible: false, + }, + + // Virtual mouse events + vmouseover: { + type: "event", + defaultValue: "", + visible: false, + }, + vmouseout: { + type: "event", + defaultValue: "", + visible: false, + }, + vmousedown: { + type: "event", + defaultValue: "", + visible: false, + }, + vmousemove: { + type: "event", + defaultValue: "", + visible: false, + }, + vmouseup: { + type: "event", + defaultValue: "", + visible: false, + }, + vclick: { + type: "event", + defaultValue: "", + visible: false, + }, + vmousecancel: { + type: "event", + defaultValue: "", + visible: false, + }, + + // Orientation change event + orientationchange: { + type: "event", + defaultValue: "", + visible: false, + }, + + // Layout events + updatelayout: { + type: "event", + defaultValue: "", + visible: false, } } }, @@ -344,6 +438,66 @@ var BWidgetRegistry = { type: "string", defaultValue: "", htmlAttribute: "data-title", + }, + pagebeforeload: { + type: "event", + defaultValue: "", + visible: false, + }, + pageload: { + type: "event", + defaultValue: "", + visible: false, + }, + pageloadfailed: { + type: "event", + defaultValue: "", + visible: false, + }, + pagebeforechange: { + type: "event", + defaultValue: "", + visible: false, + }, + pagechange: { + type: "event", + defaultValue: "", + visible: false, + }, + pagechangefailed: { + type: "event", + defaultValue: "", + visible: false, + }, + pagebeforeshow: { + type: "event", + defaultValue: "", + visible: false, + }, + pagebeforehide: { + type: "event", + defaultValue: "", + visible: false, + }, + pageshow: { + type: "event", + defaultValue: "", + visible: false, + }, + pagebeforecreate: { + type: "event", + defaultValue: "", + visible: false, + }, + pagecreate: { + type: "event", + defaultValue: "", + visible: false, + }, + pageremove: { + type: "event", + defaultValue: "", + visible: false, } }, redirect: { @@ -809,6 +963,11 @@ var BWidgetRegistry = { defaultValue: "POST", htmlAttribute: "method", forceAttribute: true + }, + submit: { + type: "event", + defaultValue: "", + visible: false, } } }, @@ -957,7 +1116,17 @@ var BWidgetRegistry = { }), nativecontrol: $.extend({}, BCommonProperties.nativecontrol, { htmlSelector: "input" - }) + }), + focus: { + type: "event", + defaultValue: "", + visible: false + }, + blur: { + type: "event", + defaultValue: "", + visible: false + } }, template: '
' }, @@ -1114,7 +1283,12 @@ var BWidgetRegistry = { }), iconpos: $.extend({}, BCommonProperties.iconpos, { defaultValue: "right" - }) + }), + change: { + type: "event", + defaultValue: "", + visible: false + } }, zones: [ { From 43e43ede3b71ed3bceb4dd0ffbcfb7ca28273f88 Mon Sep 17 00:00:00 2001 From: Xuqing Kuang Date: Wed, 22 Aug 2012 11:25:39 +0800 Subject: [PATCH 03/38] [ADM] Added force argument for generateUniqueProperty() For force generate ID property for event handler. --- src/js/adm.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/js/adm.js b/src/js/adm.js index 364f82fa..0dc26243 100644 --- a/src/js/adm.js +++ b/src/js/adm.js @@ -1960,11 +1960,15 @@ ADMNode.prototype.foreach = function (func) { * @param {String} The name of the property. * @return {String} The generated property value. */ -ADMNode.prototype.generateUniqueProperty = function (property) { +ADMNode.prototype.generateUniqueProperty = function (property, force) { var generate, design, myType, length, i, genLength, max, num, existing = []; myType = this.getType(); generate = BWidget.getPropertyAutoGenerate(myType, property); - if (!generate) { + // If force argument is set, then set the generate as myType and continue + // to run. + if (!generate && force) { + generate = myType.toLowerCase(); + } else if (!generate) { return undefined; } From 423c81bc91e58d4be2b7c4832c12f168ba5d2498 Mon Sep 17 00:00:00 2001 From: Xuqing Kuang Date: Thu, 19 Jul 2012 15:47:33 +0800 Subject: [PATCH 04/38] [General] Implemented event handler feature. Completed: 1. Event handler dialog construct 2. JS code exporting. 3. JS Preview. --- src/css/builder.css | 115 ++++++++++++ src/js/adm.js | 25 ++- src/js/serialize.js | 43 +++++ src/js/views/property.js | 380 +++++++++++++++++++++++++++++++++++++-- 4 files changed, 547 insertions(+), 16 deletions(-) diff --git a/src/css/builder.css b/src/css/builder.css index d5c76ff7..d5953079 100644 --- a/src/css/builder.css +++ b/src/css/builder.css @@ -1560,3 +1560,118 @@ input.screenCoordinate::-webkit-inner-spin-button { #exportDialog .buttonStyle { margin: 0 0.6em; } + +#eventHandlerDialog { + overflow: hidden; +} + +#eventHandlerDialog .title { + position: relative; + height: 52px; + background-color: #E4E5DF; +} + +#eventHandlerDialog .title > label { + position: absolute; + left: 10px; + top: 16px; + font-size : 13px; + color: #4d4d4d; + font-family: OpenSans-SB; + font-weight: 600; /* semi-bold */ +} + + +#eventHandlerDialog .wrap_left { + position: relative; + width: 230px; + border-right: 1px solid #dededc; +} + +#eventHandlerDialog .wrap_left .container { + position: absolute; + width: 100%; + margin: 10px; +} + + +#eventHandlerDialog .wrap_left .container * { + display: inline-block; +} + +#eventHandlerDialog .wrap_left .container select { + position: absolute; + top: 10px; + left: 0px; + width: 270px; +} + +#eventHandlerDialog .wrap_left .container fieldset { + position: absolute; + top: 40px; + width: 244px; + height: 370px; + overflow: auto; +} + +#eventHandlerDialog .wrap_left .container fieldset ul { + margin: 0px; +} + +#eventHandlerDialog .wrap_left .container fieldset li { + display: block; + padding: 8px 8px 8px 8px; + margin-left: -36px; + width: 200px; + height: 34px; + border: 8px transparent; + border-bottom: 1px solid #CCC; +} + +#eventHandlerDialog .wrap_left .container fieldset li.ui-selected { + background-color: #4AE57B; +} + +#eventHandlerDialog .wrap_left .container fieldset li > a.link { + padding-top: 8px; + width: 160px; + height: 34px; +} + +#eventHandlerDialog .wrap_left .container fieldset a.ui-button { + margin-top: 0px; + float: right; +} + +#eventHandlerDialog .wrap_left .container button.doneButton { + position: absolute; + top: 440px; + left: 46px; + width: 180px; +} + +#eventHandlerDialog .wrap_right { + width: 630px; +} + +#eventHandlerDialog .wrap_right .container { + overflow: auto; + resize: none; + height: 600px; + width: 98%; + margin: 6px; +} + +#eventHandlerDialog .wrap_right .container .CodeMirror { + margin: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + -moz-box-shadow: inset 0 0 4px #000000; + -webkit-box-shadow: inset 0 0 4px #000000; + box-shadow: inset 0 0 4px #000000; + height: 470px; +} + +#eventHandlerDialog .wrap_right .container .CodeMirror .CodeMirror-scroll { + height: 464px; +} diff --git a/src/js/adm.js b/src/js/adm.js index 0dc26243..2c4526ca 100644 --- a/src/js/adm.js +++ b/src/js/adm.js @@ -1574,6 +1574,18 @@ ADMNode.prototype.getChildrenCount = function () { return count; }; +/** + * Tests whether this node has has event handlers. + * + * @return {Boolean} True if the node has at least one event handler. + */ + +ADMNode.prototype.hasEventHandlers = function() { + return !$.isEmptyObject(this.getMatchingProperties( + {type: 'event', value: /.+/} + )); +} + /** * Tests whether this node has user-visible descendants that will be displayed * in the outline view. @@ -2234,8 +2246,9 @@ ADMNode.prototype.isPropertyExplicit = function (property) { * relevant info for performing an undo of this operation. */ ADMNode.prototype.setProperty = function (property, value, data, raw) { - var orig, func, changed, type, rval = { }, defaultValue; - type = BWidget.getPropertyType(this.getType(), property); + var orig, func, changed, rval = { }, defaultValue, + type = BWidget.getPropertyType(this.getType(), property); + if (!type) { console.error("Error: attempted to set non-existent property: " + property); @@ -2304,6 +2317,14 @@ ADMNode.prototype.setProperty = function (property, value, data, raw) { { type: "propertyChanged", node: this, property: property, oldValue: orig, newValue: value }); + + // Event handler saving after ID/event property changed. + if (property === 'id') { + if (this.hasEventHandlers()) + $.rib.saveEventHandlers(); + } else if (type === 'event') { + $.rib.saveEventHandlers(); + } rval.result = true; } return rval; diff --git a/src/js/serialize.js b/src/js/serialize.js index 64f5b2cc..5c7a4986 100644 --- a/src/js/serialize.js +++ b/src/js/serialize.js @@ -907,6 +907,48 @@ $(function () { }, error); } + /** + * Save event handler codes to main.js + * + * @param {ADMNode} design ADM design root to be serialized. + * + * @return {None} as same as addCustomFile. + */ + function saveEventHandlers(design) { + var results, id, matchedProps, eventCode, eventName, + design = design || ADM.getDesignRoot(), + jsFileName = 'js/main.js', jsType = 'js', + jsHeader = '$(document).ready(function(e) {\n', + jsContent = '', + jsFooter = '});'; + + // Regenerate the whole event javascript codes + // and save to sandbox. + results = design.findNodesByProperty( + {'type': 'event', 'value': new RegExp('.+')} + ); + $.each(results, function(index, result) { + id = result.node.getProperty('id'); + if (!id) + return + matchedProps = result.properties; + for (eventName in matchedProps) { + // Append the event code to the whole js code content. + jsContent += '$("#' + id + '").bind("' + eventName + '", function(e) {' + + '\n' + matchedProps[eventName] + '\n' + + '});\n\n'; + } + }); + if (!jsContent) { + removeSandboxHeader(jsType, jsFileName); + $.rib.fsUtils.rm(jsFileName); + return null; + } + return addCustomFile( + jsFileName, jsType, js_beautify(jsHeader + jsContent + jsFooter) + ); + } + /***************** export functions out *********************/ // Export serialization functions into $.rib namespace $.rib.generateHTML = generateHTML; @@ -919,4 +961,5 @@ $(function () { $.rib.addSandboxHeader = addSandboxHeader; $.rib.removeSandboxHeader = removeSandboxHeader; $.rib.addCustomFile = addCustomFile; + $.rib.saveEventHandlers = saveEventHandlers; }); diff --git a/src/js/views/property.js b/src/js/views/property.js index d19ef861..8c448b61 100644 --- a/src/js/views/property.js +++ b/src/js/views/property.js @@ -94,9 +94,10 @@ var labelId, labelVal, valueId, valueVal, count, widget = this, type, i, child, index, propType, p, props, options, code, o, propertyItems, label, value, + design = ADM.getDesignRoot(), title = this.element.parent().find('.property_title'), content = this.element.find('.property_content'), - continueToDelete, container; + continueToDelete, buttonsContainer, container; // Clear the properties pane when nothing is selected if (node === null || node === undefined) { @@ -380,14 +381,13 @@ .click({'p': p, 'value': value}, function(e) { var o, items = "", pages, id, value = e.data.value, p = e.data.p; - items += '
  • previous page
  • '; container = node.getParent(); while (container !== null && container.getType() !== "Page") { container = container.getParent(); } - pages = ADM.getDesignRoot().getChildren(); + pages = design.getChildren(); for (o = 0; o < pages.length; o++) { if (pages[o] === container) { continue; @@ -466,8 +466,10 @@ if (selected.length > 0) { $(this).val(selected.text()); } - value = validValue($(this), - BWidget.getPropertyType(node.getType(), updated)); + value = validValue( + node, $(this), + BWidget.getPropertyType(node.getType(), updated) + ); ret = ADM.setProperty(node, updated, value); type = node.getType(); if (ret.result === false) { @@ -481,22 +483,355 @@ }); } - // add delete element button - $('
    ') + // add buttons container + buttonsContainer = $('
    ') .addClass('property_footer') - .children('button') + .appendTo(content) + .end(); + + // Add event handler button + $('') + .addClass('buttonStyle') + .attr('id', "eventHandlerElement") + .appendTo(buttonsContainer) + .bind('click', function(e) { + var generateEventSelectElement, generateEventHandlersList, + removeEventHandler, eventLinkClicked; + var formContainer, leftPannel, leftPannelContainer, + rightPannel, eventElement, eventSelectElement, + eventEditorContainer, eventEditor, formElement, + jsCode, eventHandlersList, id, + uniqueIdName = 'id'; + + // If node have no ID property, then return directly. + if(typeof(BWidget.propertyExists(node.getType(), uniqueIdName)) == 'undefined') { + alert('Event handler must be using with the element have ID property.'); + return false; + }; + + /* + * Call back functions. + */ + + // Remove event handler + removeEventHandler = function(e) { + e.preventDefault(); + var eventName = $(this).parent().attr('rel'); + $.rib.confirm( + 'Are you sure you want to delete the ' + + eventName + + ' event handler?', + function() { + node.setProperty(eventName, ''); + if (eventElement.val() == eventName) + eventEditor.setValue(''); + formElement.trigger('submit'); + } + ); + } + + // Event link clicked callback + eventLinkClicked = function(e) { + e.preventDefault(); + var eventName = $(this).parent().attr('rel'); + eventSelectElement.val(eventName); + formElement.trigger('submit'); + } + + /* + * Elements generation functions. + */ + + // Generate the event property select options. + generateEventSelectElement = function(selectElement, matchedProps) { + var selected, newSelectElement, result, matchedProps, + optionElement, eventName, optionsGroup; + + // Store the old selected event. + selected = selectElement.val(); + + // Clean up the origin options. + selectElement.find('option').remove(); + + // Search event properties + if (!matchedProps) + matchedProps = node.getMatchingProperties( + {'type': 'event'} + ); + + // Added a initial blank option; + $('') + .addClass('cm-inactive') + .appendTo(selectElement); + + // TODO: Classify the events and use optgroup to + // genereate it. + /* + optionsGroup = $( + '' + ) + .appendTo(selectElement); + */ + + // Generate event select options. + for (eventName in matchedProps) { + optionElement = $('