diff --git a/src/css/images/widgets/jqm_list_split_button.svg b/src/css/images/widgets/jqm_list_split_button.svg
deleted file mode 100644
index 2411051f..00000000
--- a/src/css/images/widgets/jqm_list_split_button.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
diff --git a/src/css/images/widgets/jqm_text_list.svg b/src/css/images/widgets/jqm_text_list.svg
new file mode 100644
index 00000000..a093acef
--- /dev/null
+++ b/src/css/images/widgets/jqm_text_list.svg
@@ -0,0 +1,44 @@
+
+
+
diff --git a/src/css/images/widgets/jqm_text_list_button.svg b/src/css/images/widgets/jqm_text_list_button.svg
new file mode 100644
index 00000000..7bee4b5c
Binary files /dev/null and b/src/css/images/widgets/jqm_text_list_button.svg differ
diff --git a/src/css/images/widgets/jqm_text_split_list.svg b/src/css/images/widgets/jqm_text_split_list.svg
new file mode 100644
index 00000000..f9faa178
Binary files /dev/null and b/src/css/images/widgets/jqm_text_split_list.svg differ
diff --git a/src/css/images/widgets/jqm_thumbnail_list.svg b/src/css/images/widgets/jqm_thumbnail_list.svg
new file mode 100644
index 00000000..0538fc87
Binary files /dev/null and b/src/css/images/widgets/jqm_thumbnail_list.svg differ
diff --git a/src/css/images/widgets/jqm_thumbnail_list_button.svg b/src/css/images/widgets/jqm_thumbnail_list_button.svg
new file mode 100644
index 00000000..2dfb18aa
--- /dev/null
+++ b/src/css/images/widgets/jqm_thumbnail_list_button.svg
@@ -0,0 +1,26 @@
+
+
diff --git a/src/css/images/widgets/jqm_thumbnail_split_list.svg b/src/css/images/widgets/jqm_thumbnail_split_list.svg
new file mode 100644
index 00000000..309ee419
Binary files /dev/null and b/src/css/images/widgets/jqm_thumbnail_split_list.svg differ
diff --git a/src/js/adm.js b/src/js/adm.js
index 364f82fa..c83a5333 100644
--- a/src/js/adm.js
+++ b/src/js/adm.js
@@ -518,7 +518,7 @@ ADM.endTransaction = function () {
* @return {ADMNode} The child object, on success; null, on failure.
*/
ADM.addChild = function (parentRef, childRef, dryrun) {
- var parent, child;
+ var parent, child, oldParent, oldType, oldZone, oldZoneIndex;
parent = ADM.toNode(parentRef);
if (!parent) {
@@ -538,6 +538,12 @@ ADM.addChild = function (parentRef, childRef, dryrun) {
return null;
}
+ oldParent = child.getParent();
+ if (oldParent) {
+ oldType = child.getType();
+ oldZone = child.getZone();
+ oldZoneIndex = child.getZoneIndex();
+ }
if (parent.addChild(child, dryrun)) {
if (dryrun) {
return true;
@@ -547,7 +553,11 @@ ADM.addChild = function (parentRef, childRef, dryrun) {
ADM.transaction({
type: "add",
parent: child.getParent(),
- child: child
+ child: child,
+ oldParent: oldParent,
+ oldType: oldType,
+ oldZone: oldZone,
+ oldZoneIndex: oldZoneIndex
});
return child;
}
@@ -634,7 +644,7 @@ ADM.addChildRecursive = function (parentRef, childRef, dryrun) {
* @private
*/
ADM.insertChildRelative = function (siblingRef, childRef, offset, dryrun) {
- var sibling, child;
+ var sibling, child, oldParent, oldType, oldZone, oldZoneIndex;
sibling = ADM.toNode(siblingRef);
if (!sibling) {
@@ -655,6 +665,12 @@ ADM.insertChildRelative = function (siblingRef, childRef, offset, dryrun) {
childRef);
}
+ oldParent = child.getParent();
+ if (oldParent) {
+ oldType = child.getType();
+ oldZone = child.getZone();
+ oldZoneIndex = child.getZoneIndex();
+ }
if (sibling.insertChildRelative(child, offset, dryrun)) {
if (dryrun) {
return true;
@@ -663,7 +679,11 @@ ADM.insertChildRelative = function (siblingRef, childRef, offset, dryrun) {
type: "insertRelative",
sibling: sibling,
child: child,
- offset: offset
+ offset: offset,
+ oldParent: oldParent,
+ oldType: oldType,
+ oldZone: oldZone,
+ oldZoneIndex: oldZoneIndex
});
return child;
}
@@ -919,21 +939,21 @@ ADM.transaction = function (obj) {
*/
ADM.undo = function () {
var obj, undo = function (obj) {
- if (obj.type === "add") {
+ if ( ["add", "insertRelative", "move"].indexOf(obj.type) !== -1) {
ADM.ensurePageInactive(obj.child);
- obj.parent.removeChild(obj.child);
+ if (obj.oldParent) {
+ if (obj.oldType !== obj.child.getType())
+ obj.child.morphTo(obj.oldType);
+ obj.child.moveNode(obj.oldParent, obj.oldZone, obj.oldZoneIndex);
+ ADM.setSelected(obj.child);
+ }
+ else
+ obj.child.getParent().removeChild(obj.child);
}
else if (obj.type === "remove") {
obj.parent.insertChildInZone(obj.child, obj.zone, obj.zoneIndex);
ADM.setSelected(obj.child);
}
- else if (obj.type === "move") {
- obj.node.moveNode(obj.oldParent, obj.oldZone, obj.oldZoneIndex);
- ADM.setSelected(obj.node);
- }
- else if (obj.type === "insertRelative") {
- obj.sibling.getParent().removeChild(obj.child);
- }
else if (obj.type === "propertyChange") {
// TODO: this could require deeper copy of complex properties
obj.node.setProperty(obj.property, obj.oldValue, obj.data);
@@ -1167,18 +1187,8 @@ function ADMNode(widgetType) {
var currentType = widgetType, widget, zones, length, i, func;
this._valid = false;
- this._inheritance = [];
-
- while (currentType) {
- widget = BWidgetRegistry[currentType];
- if (typeof widget === "object") {
- this._inheritance.push(currentType);
- currentType = widget.parent;
- } else {
- console.error("Error: invalid type hierarchy creating ADM node");
- return;
- }
- }
+ this._inheritance = [widgetType];
+ $.merge(this._inheritance, BWidget.getAncestors(widgetType));
this._uid = ++ADMNode.prototype._lastUid;
@@ -1487,7 +1497,7 @@ ADMNode.prototype.findNodesByProperty = function (propertyFilter) {
// recurse on children
children = this.getChildren();
- for (i = children.length - 1; i >= 0; i--) {
+ for (i = 0; i < children.length; i++) {
result = result.concat(children[i].findNodesByProperty(propertyFilter));
}
return result;
@@ -1574,6 +1584,18 @@ ADMNode.prototype.getChildrenCount = function () {
return count;
};
+/**
+ * Tests whether this node has has event handlers.
+ *
+ * @return {Boolean} True if the node has at least one event handler.
+ */
+
+ADMNode.prototype.hasEventHandlers = function() {
+ return !$.isEmptyObject(this.getMatchingProperties(
+ {type: 'event', value: /.+/}
+ ));
+}
+
/**
* Tests whether this node has user-visible descendants that will be displayed
* in the outline view.
@@ -1599,6 +1621,18 @@ ADMNode.prototype.hasUserVisibleDescendants = function () {
return false;
};
+/**
+ * Change this node to another type
+ *
+ * @param {String} type The type this node will morph to.
+ * @return {ADMNode} The morphed node.
+ */
+ADMNode.prototype.morphTo = function (type) {
+ var morphedChild = ADM.createNode(type);
+ this._inheritance = morphedChild._inheritance;
+ this._zones = morphedChild._zones;
+ return this;
+};
/**
* Adds given child object to this object, generally at the end of the first
* zone that accepts the child.
@@ -1666,11 +1700,19 @@ ADMNode.prototype.addChild = function (child, dryrun) {
ADMNode.prototype.addChildToZone = function (child, zoneName, zoneIndex,
dryrun) {
// requires: assumes cardinality is "N", or a numeric string
- var add = false, myType, childType, zone, cardinality, limit;
+ var add = false, myType, childType, zone, cardinality, limit, morph,
+ morphedChildType, morphedChild;
myType = this.getType();
childType = child.getType();
zone = this._zones[zoneName];
+ morph = BWidget.getZone(myType, zoneName).morph;
+ if (morph) {
+ morphedChildType = morph(childType, myType);
+ if (morphedChildType !== childType) {
+ childType = morphedChildType;
+ }
+ }
if (!BWidget.zoneAllowsChild(myType, zoneName, childType)) {
if (!dryrun) {
console.warn("Warning: zone " + zoneName +
@@ -1693,6 +1735,7 @@ ADMNode.prototype.addChildToZone = function (child, zoneName, zoneIndex,
return false;
}
+ cardinality = cardinality.max || cardinality;
if (cardinality !== "N") {
limit = parseInt(cardinality, 10);
if (zone.length >= limit) {
@@ -1777,7 +1820,8 @@ ADMNode.prototype.insertChildInZone = function (child, zoneName, index,
}
}
- var zone = this._zones[zoneName];
+ var zone = this._zones[zoneName], oldParent,
+ myType, childType, morph, morphedChildType;
if (!zone) {
console.error("Error: zone not found in insertChildInZone: " +
zoneName);
@@ -1788,7 +1832,22 @@ ADMNode.prototype.insertChildInZone = function (child, zoneName, index,
return false;
}
if (child instanceof ADMNode) {
+ oldParent = child.getParent();
+ if (oldParent) {
+ if (oldParent === this && child.getZone() === zoneName
+ && child.getZoneIndex() < index)
+ index --;
+ return child.moveNode(this, zoneName, index, dryrun);
+ }
if (!dryrun) {
+ myType = this.getType();
+ childType = child.getType();
+ morph = BWidget.getZone(myType, zoneName).morph;
+ if (morph) {
+ morphedChildType = morph(childType, myType);
+ if (morphedChildType != childType)
+ child.morphTo(morphedChildType);
+ }
zone.splice(index, 0, child);
setRootRecursive(child, this._root);
@@ -1904,12 +1963,30 @@ ADMNode.prototype.removeChild = function (child, dryrun) {
* @return {ADMNode} The removed child, or null if not found.
*/
ADMNode.prototype.removeChildFromZone = function (zoneName, index, dryrun) {
- var zone, removed, child, parentNode, parent;
+ var zone, removed, child, parent, cardinality, min;
zone = this._zones[zoneName];
if (!zone) {
console.error("Error: no such zone found while removing child: " +
zoneName);
}
+ cardinality = BWidget.getZoneCardinality(this.getType(), zoneName);
+ if (!cardinality) {
+ console.warn("Warning: no cardinality found for zone " + zoneName);
+ return false;
+ }
+
+ if (cardinality.min) {
+ min = parseInt(cardinality.min, 10);
+
+ if (zone.length <= min) {
+ alert("At least "
+ + cardinality.min + " "
+ + BWidget.getDisplayLabel(zone[index].getType())
+ + (min === 1 ? " " : "s ")
+ + (min === 1 ? "is": "are") + " required and cannot be deleted!");
+ return false;
+ }
+ }
if (dryrun) {
removed = [zone[index]];
@@ -1960,11 +2037,15 @@ ADMNode.prototype.foreach = function (func) {
* @param {String} The name of the property.
* @return {String} The generated property value.
*/
-ADMNode.prototype.generateUniqueProperty = function (property) {
+ADMNode.prototype.generateUniqueProperty = function (property, force) {
var generate, design, myType, length, i, genLength, max, num, existing = [];
myType = this.getType();
generate = BWidget.getPropertyAutoGenerate(myType, property);
- if (!generate) {
+ // If force argument is set, then set the generate as myType and continue
+ // to run.
+ if (!generate && force) {
+ generate = myType.toLowerCase();
+ } else if (!generate) {
return undefined;
}
@@ -2230,8 +2311,9 @@ ADMNode.prototype.isPropertyExplicit = function (property) {
* relevant info for performing an undo of this operation.
*/
ADMNode.prototype.setProperty = function (property, value, data, raw) {
- var orig, func, changed, type, rval = { }, defaultValue;
- type = BWidget.getPropertyType(this.getType(), property);
+ var orig, func, changed, rval = { }, defaultValue,
+ type = BWidget.getPropertyType(this.getType(), property);
+
if (!type) {
console.error("Error: attempted to set non-existent property: " +
property);
@@ -2249,14 +2331,15 @@ ADMNode.prototype.setProperty = function (property, value, data, raw) {
}
}
- // TODO: In HTML5 the rules for ids are not this strict, so this should be
- // corrected with reference to the spec.
- // HTML id naming rules:
- // Must begin with a letter A-Z or a-z
+ // TODO: In HTML5 the rules for ids are not this strict, but JQM and jQuery don't work well
+ // with special chars, such as "$" "%" "/" accepted by HTML5.
+ //
+ // So we use the following id naming rules:
+ // Must begin with a letter A-Z or a-z or 0-9
// Can be followed by: letters (A-Za-z), digits (0-9), hyphens ("-"), and underscores ("_")
// In HTML, all values are case-insensitive
if (property == "id") {
- var pattern = /^[a-zA-Z]([\w-]*)$/;
+ var pattern = /^[a-zA-Z0-9]([\w-]*)$/;
if (value && !pattern.test(value)) {
console.error("Error: attempted to set invalid id");
return rval;
@@ -2300,6 +2383,14 @@ ADMNode.prototype.setProperty = function (property, value, data, raw) {
{ type: "propertyChanged", node: this,
property: property, oldValue: orig,
newValue: value });
+
+ // Event handler saving after ID/event property changed.
+ if (property === 'id') {
+ if (this.hasEventHandlers())
+ $.rib.saveEventHandlers();
+ } else if (type === 'event') {
+ $.rib.saveEventHandlers();
+ }
rval.result = true;
}
return rval;
diff --git a/src/js/composer.js b/src/js/composer.js
index 7f85a8c7..2e82e5b6 100644
--- a/src/js/composer.js
+++ b/src/js/composer.js
@@ -279,58 +279,6 @@ $(function() {
}
};
- var addNewNodeOnDrop = function (domParent, domChildren, adm, pid,
- nodeRef, role, domNode) {
- var newNode, domSibling, sid, admChildren, domIndex, debug;
-
- debug = (window.top.$.rib && window.top.$.rib.debug());
- domIndex = domChildren.index(domNode);
-
- // Append first(only)/last child to this container
- if (domIndex >= domChildren.length-1 || role === 'page') {
- if (adm.addChild(pid, nodeRef, true)) {
- newNode = adm.addChild(pid, nodeRef);
- debug && console.log('Appended node',role);
- newNode && adm.setSelected(newNode);
- } else {
- console.warn('Append child failed:',role);
- }
- } else if (domIndex > 0) {
- // Insert nth child into this container
- domSibling = $(domNode, domParent).prev('.adm-node');
- sid = domSibling.attr('data-uid');
- if (adm.insertChildAfter(sid, nodeRef, true)) {
- newNode = adm.insertChildAfter(sid, nodeRef);
- debug && console.log('Inserted nth node',role);
- newNode && adm.setSelected(newNode);
- } else {
- console.warn('Insert nth child failed:',role);
- }
- } else {
- // Add 1st child into an empty container
- if (domChildren.length-1 <= 0) {
- if (adm.addChild(pid, nodeRef, true)) {
- newNode = adm.addChild(pid, nodeRef);
- debug && console.log('Added 1st node',role);
- newNode && adm.setSelected(newNode);
- } else {
- console.warn('Add 1st child failed:',role);
- }
- } else {
- // Insert 1st child into non-empty container
- admChildren = adm.toNode(pid).getChildren();
- sid = admChildren.length && admChildren[0].getUid();
- if (adm.insertChildBefore(sid, nodeRef, true)) {
- newNode = adm.insertChildBefore(sid, nodeRef);
- debug && console.log('Inserted 1st node', role);
- newNode && adm.setSelected(newNode);
- } else {
- console.warn('Insert 1st child failed:', role);
- }
- }
- }
- };
-
window.top.$.rib = window.top.$.rib || {};
window.top.$.rib.dndfilter = dndfilter;
@@ -377,7 +325,7 @@ $(function() {
}
};
$(e.target).subtree().add(document)
- .unbind('click vmousedown vmousecancel vmouseup vmouseover focus'
+ .unbind('click vmousedown vmousecancel vmouseup vmouseover focus focusin'
+ ' vmouseout blur mousedown touchmove');
$(e.target).subtree('.adm-node:not(.delegation),.orig-adm-node').each(
@@ -550,6 +498,7 @@ $(function() {
'> .ui-collapsible-content > .adm-node,' +
'> ul > li.adm-node,' +
'> div > .adm-node,' +
+ '> div > div > a > .adm-node,' +
'> *.orig-adm-node:not(.ui-header,.ui-content,.ui-footer)',
start: function(event, ui){
trackOffsets('start: ',ui,$(this).data('sortable'));
@@ -634,16 +583,14 @@ $(function() {
},
stop: function(event, ui){
trackOffsets('stop: ',ui,$(this).data('sortable'));
- var type, isDrop,
+ var isDrop,
pid = $(this).attr('data-uid'),
- node = null,
adm = window.parent.ADM,
- bw = window.parent.BWidget,
root = adm.getDesignRoot(),
- node, zones, newParent, newZone,
- rdx, idx, cid, pid, sid,
+ nodeRef, newParent, newNode,
+ cid, pid,
sibling, children, parent,
- role, card;
+ role, prevItem = ui.item, nextItem = ui.item;
role = $(this).attr('data-role') || '';
@@ -694,87 +641,50 @@ $(function() {
ui.item.remove();
return false;
}
-
- // Drop from palette: add a node
if (isDrop) {
if (ui.item.data('adm-node')) {
- type = ui.item.data('adm-node').type;
+ nodeRef = ui.item.data('adm-node').type;
}
- if (!type) {
+ if (!nodeRef) {
console.warn('Drop failed: Missing node type');
ui.item.remove();
return false;
}
-
- children = $($(this).sortable('option', 'items'), this)
- .add(ui.item);
- addNewNodeOnDrop(this, children, adm, pid, type,
- role, ui.item);
- ui.item.remove();
- return;
-
- // Sorted from layoutView: move a node
- } else {
- children = ui.item.parent().children('.adm-node')
- .add(ui.item);
- idx = children.index(ui.item);
+ }
+ else {
cid = ui.item.attr('data-uid');
// closest() will select current element if it matches
// the selector, so we start with its parent.
+ nodeRef = cid && root.findNodeByUid(cid);
+ if (event && event.ctrlKey) {
+ nodeRef = adm.copySubtree(nodeRef);
+ }
+ }
+
+ while (prevItem[0] || nextItem[0]) {
+ prevItem = prevItem.prev('.adm-node');
+ if (prevItem[0] && (newNode = adm.insertChildAfter
+ (prevItem.attr('data-uid'), nodeRef)))
+ break;
+ nextItem = nextItem.next('.adm-node');
+ if (nextItem[0] && (newNode = adm.insertChildBefore
+ (nextItem.attr('data-uid'), nodeRef)))
+ break;
+ }
+ if (!prevItem[0] && !nextItem[0]) {
pid = ui.item.parent()
.closest('.adm-node.ui-sortable,'+
'.orig-adm-node.ui-sortable')
.attr('data-uid');
- node = cid && root.findNodeByUid(cid);
- if (event && event.ctrlKey) {
- node = adm.copySubtree(node); // Clone it
- addNewNodeOnDrop(this, children, adm, pid, node,
- role, ui.item);
- } else {
- newParent = pid && root.findNodeByUid(pid);
- zones = newParent && bw.getZones(newParent.getType());
- card = newParent && zones &&
- bw.getZoneCardinality(newParent.getType(),
- zones[0]);
-
- // Notify the ADM that element has been moved
- if ((zones && zones.length===1 && card !== '1')) {
- if (!node ||
- !adm.moveNode(node, newParent, zones[0],
- idx)) {
- console.warn('Move node failed');
- ui.item.remove();
- return false;
- } else {
- debug && console.log('Move node worked');
- if (node) adm.setSelected(node.getUid());
- }
- } else if (node && newParent &&
- newParent.getType() === 'Header') {
- for (var z=0; z < zones.length; z++) {
- if (adm.moveNode(node, newParent, zones[z],
- 0, true)) {
- newZone = zones[z];
- break;
- }
- }
- if (newZone) {
- adm.moveNode(node, newParent, newZone, 0);
- debug && console.log('Move node worked');
- if (node) adm.setSelected(node.getUid());
- } else {
- console.warn('Move node failed');
- ui.item.remove();
- return false;
- }
- } else {
- console.warn('Move node failed: invalid zone');
- ui.item.remove();
- return false;
- }
+ newParent = pid && root.findNodeByUid(pid);
+ if (!(newNode = adm.addChild(newParent, nodeRef))) {
+ ui.item.remove();
+ return true;
}
}
+ if (newNode)
+ adm.setSelected(newNode);
}
})
.bind('mousedown.composer', function(event) {
@@ -803,6 +713,12 @@ $(function() {
var inputs = targets.find('input');
$(inputs).disableSelection();
+ setTimeout(function(){
+ var focusElement = parent.window.focusElement;
+ if (focusElement) {
+ parent.window.$(focusElement).focus();
+ }
+ });
});
function messageHandler(e) {
diff --git a/src/js/fs.js b/src/js/fs.js
index be20e225..190cf9e9 100644
--- a/src/js/fs.js
+++ b/src/js/fs.js
@@ -48,6 +48,10 @@ $(function () {
mime: 'text/css',
suffix: ['css']
},
+ zip: {
+ mime: 'application/zip',
+ suffix: ['zip']
+ },
any: {
mime: '*',
suffix: ['*']
@@ -551,6 +555,10 @@ $(function () {
*/
checkFileType: function (type, file) {
var arrString, rule;
+ if (type === 'any') {
+ // if file type is any, we don't check
+ return true;
+ }
arrString = fsUtils.fileTypes[type.toLowerCase()].suffix.join('|');
rule = new RegExp("\\.(" + arrString + ")$", "i");
// TODO: May need to read the "content-type" to check the type
diff --git a/src/js/projects.js b/src/js/projects.js
index fbb8a852..eb2065cc 100644
--- a/src/js/projects.js
+++ b/src/js/projects.js
@@ -27,7 +27,9 @@ $(function () {
// Filter to find sandbox resources
relativeFilter:{
type: "url-uploadable",
- value: /^(?!(https?|ftp):\/+).+/i
+ value: function (valueObject) {
+ return valueObject.inSandbox;
+ }
},
// Object to save refernce count for sandbox resource
resourceRef: {},
@@ -57,7 +59,9 @@ $(function () {
defaultValue: "Default"
},
},
- themesList: {}
+ themesList: {},
+ allThemes: []
+
};
/* Asynchronous. init pmUtils.
@@ -144,6 +148,14 @@ $(function () {
},
async: false
});
+ var listThemeFiles = function (entries) {
+ // make allThemes array empty
+ pmUtils.allThemes.length = 0;
+ $.each(entries, function (index, e) {
+ pmUtils.allThemes.push(e.name);
+ });
+ };
+ $.rib.fsUtils.ls('/themes/', listThemeFiles, null);
}
/***************** APIs to manipulate projects *************************/
@@ -299,6 +311,16 @@ $(function () {
*/
pmUtils.setProperties = function (pid, properties) {
var i, pInfo, temp, newThemeName, oldThemeName, props, p;
+ var getThemeFile = function (themeName) {
+ var theme;
+ if (jQuery.inArray(themeName + ".min.css",
+ $.rib.pmUtils.allThemes) !== -1) {
+ theme = '/themes/' + themeName + ".min.css";
+ } else {
+ theme = '/themes/' + themeName + ".css";
+ }
+ return theme;
+ };
// get the original object of pInfo
pid && (pInfo = pmUtils._projectsInfo[pid]);
if (!(pid && pInfo) || typeof properties !== "object") {
@@ -313,12 +335,16 @@ $(function () {
oldThemeName = pmUtils.getProperty(pid, 'theme');
//update css property in header
if (oldThemeName === "Default") {
- $.rib.addSandboxHeader('css', '/themes/' + newThemeName + '.css');
+ newThemeName = getThemeFile(newThemeName);
+ $.rib.addSandboxHeader('css', newThemeName);
} else if (newThemeName === "Default") {
- $.rib.removeSandboxHeader('css', '/themes/' + oldThemeName + '.css');
+ oldThemeName = getThemeFile(oldThemeName);
+ $.rib.removeSandboxHeader('css', oldThemeName);
} else {
- $.rib.removeSandboxHeader('css', '/themes/' + oldThemeName + '.css');
- $.rib.addSandboxHeader('css', '/themes/' + newThemeName + '.css');
+ oldThemeName = getThemeFile(oldThemeName);
+ newThemeName = getThemeFile(newThemeName);
+ $.rib.removeSandboxHeader('css', oldThemeName);
+ $.rib.addSandboxHeader('css', newThemeName);
}
}
// if the item has schema then check the type
@@ -956,6 +982,7 @@ $(function () {
* @return {None}
*/
pmUtils.addRefCount = function (refPath) {
+ refPath = refPath.value || refPath;
if (!pmUtils.resourceRef[refPath]) {
pmUtils.resourceRef[refPath] = 1;
} else {
@@ -972,6 +999,7 @@ $(function () {
*/
pmUtils.reduceRefCount = function (refPath) {
var projectDir = pmUtils.ProjectDir + "/" + pmUtils.getActive() + "/";
+ refPath = refPath.value || refPath;
if (pmUtils.resourceRef.hasOwnProperty(refPath)) {
pmUtils.resourceRef[refPath]--;
// Delete the resource if the reference count is 0
@@ -1048,43 +1076,59 @@ $(function () {
* upload a new theme to projects
*
* @param {String} theme file
- * @return {Bool} return true if success, false when fails
+ * @param {Function} callback when upload file successfully
*/
- pmUtils.uploadTheme = function (themeFile) {
- var themeName = themeFile.name.replace(/.css$/g, "");
- var parseSwatches = function (buffer) {
- var swatches = [], lines = [], arr, i,
- re = /^\.ui-bar-([a-z]) {$/i;
- lines = buffer.split('\n');
- for (i = 0; i < lines.length; i++) {
- arr =re.exec(lines[i]);
- //if swatch is not found in swatcher list, add it into swatches
- if (arr && jQuery.inArray(arr[1], swatches) === -1) {
- swatches.push(arr[1]);
- }
+ pmUtils.uploadTheme = function (themeFile, handler) {
+ var type, reader;
+ // get file type. Currently we get file type
+ // just accordint to suffix of file name
+ var getFileType = function (fileName) {
+ var type,re = /(?:.*)+\.(zip|css)$/i;
+ type = re.exec(fileName);
+ if (type) {
+ return type[1].toLowerCase();
+ } else {
+ return "unsupported";
}
- return swatches;
};
- //write themeFile to sandbox
- $.rib.fsUtils.write('/themes/' + themeFile.name, themeFile, function () {
- //read file to buffer
- $.rib.fsUtils.read('/themes/' + themeFile.name, function (result) {
- try {
- var swatches = parseSwatches(result);
- if (swatches.length) {
- pmUtils.themesList[themeName] = swatches;
- // add default swatch into theme
- pmUtils.themesList[themeName].unshift('default');
- // update themes.json in sandbox
- $.rib.fsUtils.write('/themes.json',
- JSON.stringify(pmUtils.themesList));
+
+ type = getFileType(themeFile.name);
+ switch (type) {
+ case 'css':
+ singleCssHandler(themeFile.name, themeFile, handler);
+ break;
+ case 'zip':
+ reader = new FileReader();
+ reader.onloadend = function (e) {
+ var zip, data, cssRule, cssData, copyFiles = [];
+ // get result data from reader
+ data = e.target.result;
+ cssRule = /(\.min\.css)$/i;
+ try {
+ zip = new ZipFile(data);
+ } catch (e) {
+ console.warn("Failed to parse imported file as zip.");
}
- } catch(e) {
- alert(e.stack);
- return false;
- }
- });
- });
+ if (zip && zip.filelist) {
+ zip.filelist.forEach(function (zipInfo, idx, array) {
+ if (cssRule.test(zipInfo.filename)) {
+ cssData = zip.extract(zipInfo.filename);
+ // write themeFile to sandbox
+ singleCssHandler(zipInfo.filename.replace(/^themes\//i,""),
+ cssData, handler);
+ }
+ });
+ }
+ };
+ reader.onError = function () {
+ console.error("Read imported file error.");
+ };
+ reader.readAsBinaryString(themeFile);
+ break;
+ case 'default':
+ console.warn("unsupported file type, please upload css or zip file");
+ break;
+ }
};
/**
@@ -1100,6 +1144,103 @@ $(function () {
return themes;
};
+ /**
+ * import single css file to project
+ *
+ * @param {String} themeName imported name of theme file
+ * @param {String} content content of theme
+ * @param {Function} handler callback after handling theme
+ */
+ function singleCssHandler (themeName, content, handler) {
+ var parseSwatches = function (buffer) {
+ var swatches = [], swatch, arr =[], i,
+ re = /\.ui-bar-[a-z]/g;
+ arr = buffer.match(re);
+ for (i = 0; i < arr.length; i++) {
+ swatch = arr[i].replace(/\.ui-bar-/g, "");
+ // if swatch is not found in swatcher list, add it into swatches
+ if (swatch && jQuery.inArray(swatch, swatches) === -1) {
+ swatches.push(swatch);
+ }
+ }
+ return swatches;
+ };
+ // write theme to sandbox
+ var writeThemeFile = function (themeName, content, handler) {
+ $.rib.fsUtils.write('/themes/' + themeName, content, function () {
+ //read file to buffer
+ $.rib.fsUtils.read('/themes/' + themeName, function (buffer) {
+ var theme, swatches = [];
+ try {
+ // split suffix of '.css' and '.min.css'
+ theme = themeName.replace(/(\.min.css|\.css)$/g, "");
+ swatches = parseSwatches(buffer);
+ if (swatches.length) {
+ pmUtils.themesList[theme] = swatches;
+ // add default swatch into theme
+ pmUtils.themesList[theme].unshift('default');
+ // update themes.json in sandbox
+ $.rib.fsUtils.write('/themes.json',
+ JSON.stringify(pmUtils.themesList));
+ handler();
+ }
+ } catch(e) {
+ alert(e.stack);
+ }
+ });
+ //update allThemes
+ $.rib.pmUtils.allThemes.push(themeName);
+ });
+ };
+
+ // firstly we check whether name of imported theme file
+ // exists in the projects
+ // If exists, a confirm dialog pup up for user to select
+ // if user select "yes", orignal theme file will be deleted
+ // else do nothing, return directly
+ // else upload new theme files
+ var msg, minifiedRule = /(\.min\.css)$/i,
+ theme = themeName.replace(/(\.css|\.min.css)$/g, "");
+ if ($.rib.pmUtils.themesList.hasOwnProperty(theme) ||
+ theme === "Default") {
+ if (theme === "Default") {
+ msg = "Defauls used for JQuery Mobile default theme" +
+ "(jquery.mobile.theme-1.1.0.css), please rename" +
+ " imported theme";
+ $.rib.msgbox(msg, {"OK": null});
+ return;
+ } else {
+ msg = "There is " + theme + " theme existed in projects. " +
+ "Would you like to replace it?";
+ $.rib.confirm(msg,
+ // if user select "OK" button, replace old one with new theme
+ function () {
+ var anotherThemeFile;
+ if (minifiedRule.test(themeName)) {
+ anotherThemeFile = themeName.replace(/\.min\.css$/g, "\.css");
+ } else {
+ anotherThemeFile = themeName.replace(/\.css$/g, ".min.css");
+ }
+ if (jQuery.inArray(anotherThemeFile, pmUtils.allThemes) !== -1) {
+ $.rib.fsUtils.rm('/themes/' + anotherThemeFile,
+ function () {
+ var idx, allThemes = $.rib.pmUtils.allThemes;
+ idx = allThemes.indexOf(anotherThemeFile);
+ if(idx!=-1) {
+ allThemes.splice(idx, 1);
+ }
+ writeThemeFile(themeName, content, handler);
+ });
+ } else {
+ writeThemeFile(themeName, content, handler);
+ }
+ });
+ }
+ } else {
+ writeThemeFile(themeName, content, handler);
+ }
+ }
+
/************ export pmUtils to $.rib **************/
$.rib.pmUtils = pmUtils;
});
diff --git a/src/js/serialize.js b/src/js/serialize.js
index 64f5b2cc..89af4beb 100644
--- a/src/js/serialize.js
+++ b/src/js/serialize.js
@@ -425,11 +425,23 @@ $(function () {
}
function getDesignHeaders(design, useSandboxUrl) {
- var i, props, el, designRoot, headers;
+ var i, props, el, designRoot, headers, toCorrectPath;
designRoot = design || ADM.getDesignRoot();
headers = [];
props = designRoot.getProperty('metas');
+ toCorrectPath = function (header) {
+ var path = header.value;
+ // If need to use sandbox url
+ if (header.inSandbox) {
+ if (useSandboxUrl) {
+ path = toSandboxUrl(path);
+ } else {
+ path = path.replace(/^\//, '');
+ }
+ }
+ return path;
+ };
for (i in props) {
// Skip design only header properties
if (props[i].hasOwnProperty('designOnly') && props[i].designOnly) {
@@ -444,12 +456,7 @@ $(function () {
if ((typeof props[i].value !== 'string') || (props[i].value.length <= 0)) {
continue;
}
- // If need to use sandbox url
- if (useSandboxUrl && props[i].inSandbox) {
- el = el + '="' + toSandboxUrl(props[i].value) + '"';
- } else {
- el = el + '="' + props[i].value + '"';
- }
+ el = el + '="' + toCorrectPath(props[i]) + '"';
if (props[i].hasOwnProperty('content')) {
el = el + ' content="' + props[i].content + '"';
}
@@ -470,11 +477,7 @@ $(function () {
}
el = '';
headers.push(el);
}
@@ -492,11 +495,7 @@ $(function () {
}
el = '';
headers.push(el);
}
@@ -659,7 +658,7 @@ $(function () {
for (p in matched) {
files.push({
"src": toSandboxUrl(matched[p]),
- "dst": matched[p]
+ "dst": matched[p].value ? matched[p].value : matched[p]
});
}
});
@@ -730,19 +729,23 @@ $(function () {
}
function toSandboxUrl(path, pid) {
- var projectDir, fullPath;
+ var projectDir, pathStr;
pid = pid || $.rib.pmUtils.getActive();
projectDir = $.rib.pmUtils.getProjectDir(pid);
- if (typeof path !== "string") {
- console.error("Invalid path in toSandboxUrl: " + path);
+ if ((path instanceof Object) && path.inSandbox) {
+ pathStr = path.value;
+ } else {
+ pathStr = path;
+ }
+ if (typeof pathStr !== "string") {
+ console.error("Invalid path in toSandboxUrl: " + pathStr);
return null;
}
- fullPath = path;
// If the first char is '/', then it will be the absolute path in sandbox
- if (path[0] !== '/' && projectDir) {
- fullPath = projectDir + path;
+ if (pathStr[0] !== '/' && projectDir) {
+ pathStr = projectDir + pathStr;
}
- return $.rib.fsUtils.pathToUrl(fullPath);
+ return $.rib.fsUtils.pathToUrl(pathStr);
}
function indexOfArray(array, value) {
@@ -907,6 +910,48 @@ $(function () {
}, error);
}
+ /**
+ * Save event handler codes to main.js
+ *
+ * @param {ADMNode} design ADM design root to be serialized.
+ *
+ * @return {None} as same as addCustomFile.
+ */
+ function saveEventHandlers(design) {
+ var results, id, matchedProps, eventCode, eventName,
+ design = design || ADM.getDesignRoot(),
+ jsFileName = 'js/main.js', jsType = 'js',
+ jsHeader = '$(document).ready(function(e) {\n',
+ jsContent = '',
+ jsFooter = '});';
+
+ // Regenerate the whole event javascript codes
+ // and save to sandbox.
+ results = design.findNodesByProperty(
+ {'type': 'event', 'value': new RegExp('.+')}
+ );
+ $.each(results, function(index, result) {
+ id = result.node.getProperty('id');
+ if (!id)
+ return
+ matchedProps = result.properties;
+ for (eventName in matchedProps) {
+ // Append the event code to the whole js code content.
+ jsContent += '$("#' + id + '").bind("' + eventName + '", function(e) {'
+ + '\n' + matchedProps[eventName] + '\n'
+ + '});\n\n';
+ }
+ });
+ if (!jsContent) {
+ removeSandboxHeader(jsType, jsFileName);
+ $.rib.fsUtils.rm(jsFileName);
+ return null;
+ }
+ return addCustomFile(
+ jsFileName, jsType, js_beautify(jsHeader + jsContent + jsFooter)
+ );
+ }
+
/***************** export functions out *********************/
// Export serialization functions into $.rib namespace
$.rib.generateHTML = generateHTML;
@@ -919,4 +964,5 @@ $(function () {
$.rib.addSandboxHeader = addSandboxHeader;
$.rib.removeSandboxHeader = removeSandboxHeader;
$.rib.addCustomFile = addCustomFile;
+ $.rib.saveEventHandlers = saveEventHandlers;
});
diff --git a/src/js/views/layout.js b/src/js/views/layout.js
index e925c36c..98fa2f0f 100644
--- a/src/js/views/layout.js
+++ b/src/js/views/layout.js
@@ -428,15 +428,6 @@
// these lines.
$(domNode).removeAttr('disabled');
$(domNode).children().removeAttr('disabled');
-
- // Set default src for empty image to make them show up
- // TODO: This case may need to improve, such as we can show a box
- // with "empty image" message to notice the user
- if ((admNode.getType() === "Image") && (!admNode.getProperty('src'))) {
- if ($(domNode).is('img')) {
- $(domNode).attr('src', "src/css/images/widgets/tizen_image.svg");
- }
- }
}
});
})(jQuery);
diff --git a/src/js/views/outline.js b/src/js/views/outline.js
index 855bfa72..765b39e9 100644
--- a/src/js/views/outline.js
+++ b/src/js/views/outline.js
@@ -102,7 +102,6 @@
}
widget.removeNode(event.node);
widget.addNode(event.node);
- widget.setSelected(widget._getSelected());
break;
default:
console.warning('Unknown type of modelUpdated event:'
@@ -157,6 +156,8 @@
},
_getSelected: function () {
var model = this.options.model;
+ if (!model)
+ return null;
return model.getSelectedNode() || model.getActivePage();
},
_renderPageNode: function (domNode, node) {
diff --git a/src/js/views/project.js b/src/js/views/project.js
index 99c33a1b..85a35f1f 100644
--- a/src/js/views/project.js
+++ b/src/js/views/project.js
@@ -249,18 +249,26 @@
.appendTo(projectDialog, this);
$(document).delegate('#uploadTheme', "click", function () {
- $.rib.fsUtils.upload('css', projectDialog, function(file) {
- pmUtils.uploadTheme(file);
- //if file.name isn't in theme select list, add it to the list
- //else do nothing
- var themeName = file.name.replace(/.css$/g, "");
- themeNames = pmUtils.getAllThemes();
- if (jQuery.inArray(themeName, themeNames) === -1) {
- themeNames.push(themeName);
- $('')
- .appendTo('#themePicker', projectDialog);
- }
+ $.rib.fsUtils.upload('any', projectDialog, function(file) {
+ var handler = function () {
+ var themeName, themeList = [];
+
+ // get current themes from select element
+ projectDialog.find('#themePicker option').each(function () {
+ themeList.push($(this).val());
+ });
+ for (themeName in pmUtils.themesList) {
+ // if imported theme file isn't in theme select list, add it
+ // to the list else do nothing
+ if (jQuery.inArray(themeName, themeList) === -1) {
+ $('')
+ .appendTo('#themePicker', projectDialog);
+ }
+ }
+ };
+
+ pmUtils.uploadTheme(file, handler);
});
});
projectDialog.dialog({
@@ -411,7 +419,7 @@
// and open it, create a new project in no project case
if (!$.rib.pmUtils.getActive()) {
$(document.body).one("tabsselect", function (e, tab) {
- if (tab.index === 1) {
+ if (tab.index !== 0) {
$.rib.pmUtils.showLastOpened();
}
});
diff --git a/src/js/views/property.js b/src/js/views/property.js
index d19ef861..9157a644 100644
--- a/src/js/views/property.js
+++ b/src/js/views/property.js
@@ -39,6 +39,10 @@
* 0.4);
el.height(newHeight);
});
+ this.element.delegate('*', 'focus', function(e){
+ window.focusElement = this;
+ e.stopPropagation();
+ });
return this;
},
@@ -80,13 +84,28 @@
widget.refresh(event,widget);
},
+ _setProperty: function(property, value) {
+ var viewId = property + '-value';
+ this.element.find("#" + viewId).val(value);
+ },
+
_modelUpdatedHandler: function(event, widget) {
+ var affectedWidget, id;
+
widget = widget || this;
- if (event && (event.type === "propertyChanged" &&
- event.node.getType() === 'Design')) {
- return;
+ if (event && event.type === "propertyChanged") {
+ if (event.node.getType() === 'Design') {
+ return;
+ }
+ widget._setProperty(event.property, event.newValue);
} else {
widget.refresh(event,widget);
+ if (event.type === 'propertyChanged') {
+ id = event.property + '-value';
+ affectedWidget = widget.element.find('#' + id);
+ affectedWidget[0].scrollIntoViewIfNeeded();
+ affectedWidget.effect("highlight", {}, 1000);
+ }
}
},
@@ -94,9 +113,10 @@
var labelId, labelVal, valueId, valueVal, count,
widget = this, type, i, child, index, propType,
p, props, options, code, o, propertyItems, label, value,
+ design = ADM.getDesignRoot(),
title = this.element.parent().find('.property_title'),
content = this.element.find('.property_content'),
- continueToDelete, container;
+ continueToDelete, buttonsContainer, container, range, min, max;
// Clear the properties pane when nothing is selected
if (node === null || node === undefined) {
@@ -163,24 +183,49 @@
value.find("#" + valueId).attr("checked", "checked");
}
break;
+ case "integer":
+ range = BWidget.getPropertyRange(type, p);
+ if (range) {
+ min = range.split('-')[0];
+ max = range.split('-')[1];
+ $('')
+ .addClass('title labelInput')
+ .attr({
+ id: valueId,
+ min: min,
+ max: max
+ })
+ .change(function(event) {
+ if( parseInt(this.value) > parseInt(this.max) ||
+ parseInt(this.value) < parseInt(this.min)) {
+ $(this).effect("highlight", {color: "red"}, 1000);
+ event.stopImmediatePropagation();
+ this.value = valueVal;
+ }
+ })
+ .appendTo(value);
+ //set default value
+ value.find('#' + valueId).val(valueVal);
+ }
+ break;
case "url-uploadable":
$('')
.attr('id', valueId)
.addClass('title labelInput')
.appendTo(value);
//set default value
- value.find('#' + valueId).val(valueVal);
+ value.find('#' + valueId).val(valueVal.value);
$('')
.addClass('buttonStyle')
- .click(function (e) {
- var target, saveDir;
- target = $(this).prev("input:text");
- saveDir = $.rib.pmUtils.ProjectDir + "/" + $.rib.pmUtils.getActive() + "/images/";
+ .click({node:node, property:p}, function (e) {
+ var saveDir = $.rib.pmUtils.getProjectDir() + "images/";
$.rib.fsUtils.upload("image", $(this).parent(), function(file) {
// Write uploaded file to sandbox
$.rib.fsUtils.write(saveDir + file.name, file, function (newFile) {
- target.val("images/" + newFile.name);
- target.trigger('change');
+ ADM.setProperty(e.data.node, e.data.property, {
+ inSandbox: true,
+ value: "images/" + newFile.name
+ });
});
});
}).appendTo(value);
@@ -248,9 +293,8 @@
.end().end()
.append(' | ')
.children().eq(3)
- .append('
')
+ .append('Delete
')
.children(':first')
- .attr('src', "src/css/images/deleteButton_up.png")
// add delete option handler
.click(function(e) {
try {
@@ -380,14 +424,13 @@
.click({'p': p, 'value': value}, function(e) {
var o, items = "", pages, id,
value = e.data.value, p = e.data.p;
-
items += 'previous page';
container = node.getParent();
while (container !== null &&
container.getType() !== "Page") {
container = container.getParent();
}
- pages = ADM.getDesignRoot().getChildren();
+ pages = design.getChildren();
for (o = 0; o < pages.length; o++) {
if (pages[o] === container) {
continue;
@@ -466,12 +509,14 @@
if (selected.length > 0) {
$(this).val(selected.text());
}
- value = validValue($(this),
- BWidget.getPropertyType(node.getType(), updated));
+ value = validValue(
+ node, $(this),
+ BWidget.getPropertyType(node.getType(), updated)
+ );
ret = ADM.setProperty(node, updated, value);
type = node.getType();
if (ret.result === false) {
- $(this).val(node.getProperty(updated));
+ $(this).effect("highlight", {color: "red"}, 1000).val(node.getProperty(updated));
} else if (type === "Button" &&
value === "previous page") {
ADM.setProperty(node, "opentargetas", "default");
@@ -481,22 +526,355 @@
});
}
- // add delete element button
- $('')
+ // add buttons container
+ buttonsContainer = $('')
.addClass('property_footer')
- .children('button')
+ .appendTo(content)
+ .end();
+
+ // Add event handler button
+ $('
')
+ .addClass('buttonStyle')
+ .attr('id', "eventHandlerElement")
+ .appendTo(buttonsContainer)
+ .bind('click', function(e) {
+ var generateEventSelectElement, generateEventHandlersList,
+ removeEventHandler, eventLinkClicked;
+ var formContainer, leftPannel, leftPannelContainer,
+ rightPannel, eventElement, eventSelectElement,
+ eventEditorContainer, eventEditor, formElement,
+ jsCode, eventHandlersList, id,
+ uniqueIdName = 'id';
+
+ // If node have no ID property, then return directly.
+ if(typeof(BWidget.propertyExists(node.getType(), uniqueIdName)) == 'undefined') {
+ alert('Event handler must be using with the element have ID property.');
+ return false;
+ };
+
+ /*
+ * Call back functions.
+ */
+
+ // Remove event handler
+ removeEventHandler = function(e) {
+ e.preventDefault();
+ var eventName = $(this).parent().attr('rel');
+ $.rib.confirm(
+ 'Are you sure you want to delete the '
+ + eventName
+ + ' event handler?',
+ function() {
+ node.setProperty(eventName, '');
+ if (eventElement.val() == eventName)
+ eventEditor.setValue('');
+ formElement.trigger('submit');
+ }
+ );
+ }
+
+ // Event link clicked callback
+ eventLinkClicked = function(e) {
+ e.preventDefault();
+ var eventName = $(this).parent().attr('rel');
+ eventSelectElement.val(eventName);
+ formElement.trigger('submit');
+ }
+
+ /*
+ * Elements generation functions.
+ */
+
+ // Generate the event property select options.
+ generateEventSelectElement = function(selectElement, matchedProps) {
+ var selected, newSelectElement, result, matchedProps,
+ optionElement, eventName, optionsGroup;
+
+ // Store the old selected event.
+ selected = selectElement.val();
+
+ // Clean up the origin options.
+ selectElement.find('option').remove();
+
+ // Search event properties
+ if (!matchedProps)
+ matchedProps = node.getMatchingProperties(
+ {'type': 'event'}
+ );
+
+ // Added a initial blank option;
+ $('
')
+ .addClass('cm-inactive')
+ .appendTo(selectElement);
+
+ // TODO: Classify the events and use optgroup to
+ // genereate it.
+ /*
+ optionsGroup = $(
+ '
'
+ )
+ .appendTo(selectElement);
+ */
+
+ // Generate event select options.
+ for (eventName in matchedProps) {
+ optionElement = $('