diff --git a/src/css/builder.css b/src/css/builder.css index d5c5d2ff..80ded261 100644 --- a/src/css/builder.css +++ b/src/css/builder.css @@ -1563,3 +1563,88 @@ input.screenCoordinate::-webkit-inner-spin-button { #exportDialog .buttonStyle { margin: 0 0.6em; } + +#eventHandlerDialog { + overflow: hidden; + max-height: 380px; +} + +#eventHandlerDialog .wrap_left { + position: relative; + width: 221px; + border-right: 1px solid #dededc; +} + +#eventHandlerDialog .wrap_right { + width: 548px; +} + +#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 .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: 200px; +} + +#eventHandlerDialog .wrap_left .container a.emptyLink { + position: absolute; + top: 46px; + left: 110px; +} + +#eventHandlerDialog .wrap_left .container button.emptyButton { + position: absolute; + top: 40px; + left: 160px; +} + +#eventHandlerDialog .wrap_left .container button.doneButton { + position: absolute; + top: 250px; + left: 10px; + width: 180px; +} + +#eventHandlerDialog .wrap_right .container { + overflow: auto; + resize: none; + height: 600px; + width: 530px; + 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; +} diff --git a/src/js/adm.js b/src/js/adm.js index 17f28374..5ea5fcd5 100644 --- a/src/js/adm.js +++ b/src/js/adm.js @@ -1919,12 +1919,16 @@ 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) { return undefined; + } else { + generate = myType; } // find existing nodes with this property set diff --git a/src/js/serialize.js b/src/js/serialize.js index 5abbbc66..7f19a58d 100644 --- a/src/js/serialize.js +++ b/src/js/serialize.js @@ -462,17 +462,23 @@ $(function () { if (props[i].hasOwnProperty('key')) { el = el + props[i].key; } - // If need to use sandbox url - if (useSandboxUrl && props[i].inSandbox) { - el = el + '="' + toSandboxUrl(props[i].value) + '"'; - } else if (props[i].hasOwnProperty('value')) { - el = el + '="' + props[i].value + '"'; - } - if (props[i].hasOwnProperty('content')) { - el = el + ' content="' + props[i].content + '"'; + if (props[i].hasOwnProperty('value') && props[i].value) { + // Skip empty or invalid header properties + if ((typeof props[i].value !== 'string') || (props[i].value.length <= 0)) { + continue; + } + // If need to use sandbox url + if (useSandboxUrl && props[i].inSandbox) { + el = el + '="' + toSandboxUrl(props[i].value) + '"'; + } else { + el = el + '="' + props[i].value + '"'; + } + if (props[i].hasOwnProperty('content')) { + el = el + ' content="' + props[i].content + '"'; + } + el = el + '>'; + headers.push(el); } - el = el + '>'; - headers.push(el); } props = designRoot.getProperty('libs'); for (i in props) { @@ -480,15 +486,21 @@ $(function () { if (props[i].hasOwnProperty('designOnly') && props[i].designOnly) { continue; } - el = ''; + headers.push(el); } - el = el + '>'; - headers.push(el); } props = designRoot.getProperty('css'); for (i in props) { @@ -496,15 +508,21 @@ $(function () { if (props[i].hasOwnProperty('designOnly') && props[i].designOnly) { continue; } - el = ''; + headers.push(el); } - el = el + ' rel="stylesheet">'; - headers.push(el); } return headers; } @@ -620,9 +638,12 @@ $(function () { if (headers[header].hasOwnProperty('designOnly') && headers[header].designOnly) { continue; } + if (!headers[header].value) { + continue; + } if (headers[header].inSandbox) { files.push({ - 'src': toSandboxUrl(headers[header].Value), + 'src': toSandboxUrl(headers[header].value), 'dst': headers[header].value }); } else { @@ -747,7 +768,85 @@ $(function () { return $.rib.fsUtils.pathToUrl(fullPath); } - /***************** export functions out *********************/ + /** + * Update header file in current design, such as css, js file. + * This function's logic: try to replace the oldFile with newFile + * in design headers, but if the oldFile in not in the list, then + * just insert the newFile, or if the newFile is not specified or + * it has already in the list, then just remove the oldFile. + * + * @param {String} type Type of the specified header. + * @param {String} oldFile Sandbox path of the old file to be updated. + * @param {String} newFile Sandbox path of the new file. + * Notes: filePath If the file is in project directory, relative path should + * be used. A path beginning with '/' will be considered as absolut + * path in sandbox. + * + * @return {None} + */ + + function updateHeaderFile(type, oldFile, newFile) { + var property, array, design, i, + oldIndex = -1, newIndex = -1, + propertyMap = { + css: 'css', + js: 'libs' + }; + if ((newFile === oldFile) || (!oldFile && !newFile)) { + return; + } + property = propertyMap[type]; + if (!property) { + dumplog('warning: No header:' + type + 'in design.'); + } + design = ADM.getDesignRoot(); + array = $.merge([], design.getProperty(property)); + for (i = 0; i < array.length; i++) { + if (!array[i].inSandbox) { + continue; + } else { + if (oldFile && (array[i].value === oldFile)) { + oldIndex = i; + if ((!newFile) && (newIndex >= 0)) { + break; + } + } + if (newFile && (array[i].value === newFile)) { + newIndex = i; + if ((!oldIndex) && (oldIndex >= 0)) { + break; + } + } + } + } + // The oldFile in headers list. + if (oldIndex >= 0) { + // Update case: need to insert new file and it's not + // in the list, then delete old one, insert the new one. + if (newFile && newIndex === -1) { + array.splice(oldIndex, 1, { + inSandbox: true, + value: newFile + }); + } else { + // Remove case: just delete the old one if don't need to insert. + array.splice(oldIndex, 1); + } + // set the new array back + design.setProperty(property, array); + } else if (newFile && (oldIndex === -1) && (newIndex === -1)) { + // Add case: need to insert new one, and old one and new one + // are both not list, then we just add the new one. + array.push({ + inSandbox: true, + value: newFile + }); + // set the new array back + design.setProperty(property, array); + } + return; + } + /** * Add custom file to current active project. * It will save the content in project folder. If the parent directy of @@ -761,7 +860,7 @@ $(function () { * * @return {None} */ - $.rib.addCustomFile = function (filePath, type, contents, success, error) { + function addCustomFile(filePath, type, contents, success, error) { var destPath, addToDesign, projectDir; projectDir = $.rib.pmUtils.getProjectDir(); // If it is relative path, then add the project folder path @@ -770,42 +869,14 @@ $(function () { } else { destPath = filePath; } - addToDesign = function (type, value) { - var design, array, property, propertyMap, i, temp; - propertyMap = { - css: 'css', - js: 'libs' - }; - design = ADM.getDesignRoot(); - property = propertyMap[type]; - temp = $.extend(true, {}, { - property: property, - value: design.getProperty(property) - }); - array = temp.value; - for (i = 0; i < array.length; i++) { - // If the value is in headers, then just return. - if (JSON.stringify(array[i]) === JSON.stringify(value)) { - return; - } - } - // If the value is not in array, then push the value in the list - array.push(value); - // set the new array back - design.setProperty(property, array); - return; - }; // Write contents to sandbox $.rib.fsUtils.write(destPath, contents, function (newFile) { - var headerValue = { - 'inSandbox': true, - 'value': filePath - }; - addToDesign(type, headerValue); + updateHeaderFile(type, null, filePath); success && success(newFile); }, error); - }; + } + /***************** export functions out *********************/ // Export serialization functions into $.rib namespace $.rib.generateHTML = generateHTML; $.rib.serializeADMSubtreeToDOM = serializeADMSubtreeToDOM; @@ -813,4 +884,6 @@ $(function () { $.rib.JSONToProj = JSONToProj; $.rib.getDesignHeaders = getDesignHeaders; $.rib.exportPackage = exportPackage; + $.rib.updateHeaderFile = updateHeaderFile; + $.rib.addCustomFile = addCustomFile; }); diff --git a/src/js/views/property.js b/src/js/views/property.js index 13916b81..3d540bc6 100644 --- a/src/js/views/property.js +++ b/src/js/views/property.js @@ -106,9 +106,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; + continueToDelete, buttonsContainer; // Clear the properties pane when nothing is selected if (node === null || node === undefined) { @@ -396,7 +397,7 @@ var o, items = "", pages, id, value = e.data.value, p = e.data.p; - pages = ADM.getDesignRoot().getChildren(); + pages = design.getChildren(); for (o = 0; o < pages.length; o++) { id = pages[o].getProperty('id'); items += '
  • #' + id + '
  • '; @@ -479,15 +480,17 @@ }); } - // add delete element button - $('
    ') + // add buttons container + buttonsContainer = $('
    ') .addClass('property_footer') - .children('button') + .appendTo(content) + .end(); + + // add delete element button + $('') .addClass('buttonStyle') .attr('id', "deleteElement") - .end() - .appendTo(content); - content.find('#deleteElement') + .appendTo(buttonsContainer) .bind('click', function (e) { var parent, zone, index, msg; var doDelete = function () { @@ -531,6 +534,231 @@ return false; }); + // Add event handler button + $('') + .addClass('buttonStyle') + .attr('id', "eventHandlerElement") + .appendTo(buttonsContainer) + .bind('click', function(e) { + var formContainer, leftPannel, leftPannelContainer, + rightPannel, eventElement, eventSelectElement, + emptyEditorContentFunc, eventEditorContainer, + eventEditor, formElement, jsCode, 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; + }; + + // Construct the page layout + formContainer = $('
    '); + leftPannel = $('
    ') + .appendTo(formContainer) + rightPannel =$('
    ') + .appendTo(formContainer) + + /*** Left pannel contents ***/ + + $('
    ') + .appendTo(leftPannel); + + leftPannelContainer = $('
    ') + .addClass('container') + .appendTo(leftPannel); + + // Construct event options elements + eventSelectElement = $('' + ).appendTo(leftPannelContainer); + + // Intial the the trash button + emptyEditorContentFunc = function (e) { + e.preventDefault(); + var askClean = $.rib.confirm( + 'Are you sure to clean the javascript codes?', + function() { + eventEditor.setValue(''); + } + ); + }; + + $('Delete') + .addClass('emptyLink') + .click(emptyEditorContentFunc) + .appendTo(leftPannelContainer); + + $('') + .addClass('emptyButton') + .button({ + text: false, + icons: { + primary: "ui-icon-trash" + } + }) + .click(emptyEditorContentFunc) + .appendTo(leftPannelContainer); + + // Create the DONE button + $('') + .addClass('buttonStyle doneButton') + .click( function (e) { + formElement.dialog('close'); + }) + .button() + .appendTo(leftPannelContainer); + + /*** Right pannel contents ***/ + + $('
    ') + .appendTo(rightPannel); + + // Construct code editor element + eventEditorContainer = $('
    ') + .addClass('container') + .appendTo(rightPannel); + eventEditor = CodeMirror( + eventEditorContainer[0], + { + mode: "javascript", + readOnly: 'nocursor', + } + ); + eventEditorContainer.show(); + + /*** Dialog contents ***/ + + formElement = $('
    ') + .attr('id', 'eventHandlerDialog') + .append(formContainer) + .dialog({ + title: "Event Handlers(" + + BWidget.getDisplayLabel(type) + + (node.getProperty('id') ? + "#" + node.getProperty('id'):'') + ")", + modal: true, + width: 769, + height: 460, + resizable: false + }) + .bind('dialogclose', function(e) { + $(this).trigger('submit'); + }) + .bind('saveEventsToFile', function(e) { + var results, result, matchedProps, id, eventCode, + eventName, + jsCode = '$(document).ready(function(e) {\n'; + + // Regenerate the whole event javascript codes + // and save to sandbox. + results = design.findNodesByProperty( + {'type': 'event'} + ); + for (var i=0; (result = results[i]); i++) { + id = result.node.getProperty('id'); + if (!id) + continue + matchedProps = result.properties; + for (eventName in matchedProps) { + eventCode = matchedProps[eventName]; + // If ID property or eventCode is not defined, then next. + if (!eventCode) + continue; + // Append the event code to the whole js code content. + jsCode += '$("#' + id + '").bind("' + eventName + '", function(e) {' + + '\n' + eventCode + '\n' + + '});\n\n'; + } + } + jsCode += '});'; + // Make the JS code more beautiful. + jsCode = js_beautify(jsCode); + $.rib.addCustomFile( + 'js/main.js', 'js', jsCode + ); + }) + .bind('submit', function(e) { + e.preventDefault(); + + // Serialize the form data to JSON. + var formData = $(this).serializeJSON(); + formData['jsCode'] = eventEditor.getValue(); + + // If ID is blank, generate a unique one. + if(node.getProperty(uniqueIdName) == '') { + node.generateUniqueProperty( + uniqueIdName, true + ); + } + + // Save editor content to ADM property. + if (formData.currentEvent) { + node.setProperty( + formData.currentEvent, + formData.jsCode + ); + // Regenerate main.js file. + $(this).trigger('saveEventsToFile'); + } + + // Load the jsCode + // + // Checking the event select element changed + // + // If old event is not equal to current event in + // select, it's meaning the select changed not + // the window close, so we need to load the JS + // code from new selected property and change the + // editor content. + if (formData.currentEvent != formData.selectedEvent) { + if (formData.selectedEvent) { + // Load the event property content and set + // the editor content. + jsCode = node.getProperty( + formData.selectedEvent + ); + if (typeof(jsCode) != 'string') + jsCode = ''; + eventEditor.setOption('readOnly', false); + eventEditor.setValue(jsCode); + } else { + // Check the selection of event, if + // selected blank will clean up the editor + // content and set editor to be read only. + eventEditor.setValue(''); + eventEditor.setOption( + 'readOnly', 'nocursor' + ); + } + + // Set currentEvent element to selctedEvent. + eventElement.val(formData.selectedEvent); + }; + }); + }); + function validValue(element, type) { var ret = null, value = element.val(); switch (type) { diff --git a/src/js/widgets.js b/src/js/widgets.js index e8953786..f75eaf4f 100644 --- a/src/js/widgets.js +++ b/src/js/widgets.js @@ -209,6 +209,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, } } }, @@ -340,7 +434,67 @@ 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: { zone: "content", @@ -805,6 +959,11 @@ var BWidgetRegistry = { defaultValue: "POST", htmlAttribute: "method", forceAttribute: true + }, + submit: { + type: "event", + defaultValue: "", + visible: false, } } }, @@ -1110,7 +1269,12 @@ var BWidgetRegistry = { }), iconpos: $.extend({}, BCommonProperties.iconpos, { defaultValue: "right" - }) + }), + change: { + type: "event", + defaultValue: "", + visible: false + } }, zones: [ {