Commit 048f69a4 authored by Sven Franck's avatar Sven Franck

added breadcrumbs, fixed infoFields and broken deeplink

parent 7a48da05
...@@ -658,15 +658,26 @@ ...@@ -658,15 +658,26 @@
content, content,
container, container,
promises = [], promises = [],
path = content_dict.property_dict.path_dict, path_flag = content_dict.property_dict.path_dict,
status_flag = content_dict.property_dict.status_dict,
multi_language_flag = content_dict.property_dict.i18n; multi_language_flag = content_dict.property_dict.i18n;
// setup loader
if (status_flag) {
switch (status_flag.type) {
case "loader": {
app.default_dict.loader = true;
app.default_dict.loader_theme = status_flag.theme;
}
};
}
// create path library // create path library
// TODO: should this be optional? // TODO: should this be optional?
if (path) { if (path_flag) {
for (single_path in path) { for (single_path in path_flag) {
if (path.hasOwnProperty(single_path)) { if (path_flag.hasOwnProperty(single_path)) {
app.default_dict.path_dict[single_path] = path[single_path]; app.default_dict.path_dict[single_path] = path_flag[single_path];
} }
} }
} else { } else {
...@@ -795,7 +806,16 @@ ...@@ -795,7 +806,16 @@
if (page_dict === undefined) { if (page_dict === undefined) {
util.errorHandler({"error":"Pageindex: Missing page definition"}); util.errorHandler({"error":"Pageindex: Missing page definition"});
} else { } else {
return app.setContent(page_dict, url_dict, create);
// NOTE: 3rd parameter "create" needs to be set to undefined,
// if a page already exists. Setting to true generates a new page
// setting to false tries to update the content section of an
// existing page (whose contents may not exist at this moment)
return app.setContent(
page_dict,
url_dict,
document.getElementById(url_dict.id) ? undefined : true
);
} }
return; return;
}, },
...@@ -2372,6 +2392,7 @@ ...@@ -2372,6 +2392,7 @@
factory.page = function (content_dict, url_dict, create) { factory.page = function (content_dict, url_dict, create) {
var i, var i,
j, j,
k,
last, last,
element, element,
wrapper, wrapper,
...@@ -2382,7 +2403,8 @@ ...@@ -2382,7 +2403,8 @@
if (content_dict.children) { if (content_dict.children) {
for (i = 0; i < content_dict.children.length; i += 1) { for (i = 0; i < content_dict.children.length; i += 1) {
promises[i] = app.setContent(content_dict.children[i]); // NOTE: need to pass url and create info
promises[i] = app.setContent(content_dict.children[i], url_dict, create);
} }
} }
...@@ -2420,7 +2442,15 @@ ...@@ -2420,7 +2442,15 @@
} }
); );
// assemble // update header before changing to a new page
if (create === true) {
app.setPageTitle(
content_dict.title_i18n,
target.querySelectorAll("div.ui-header")
);
}
wrapper.appendChild(app.breadcrumb(url_dict));
wrapper.appendChild(target); wrapper.appendChild(target);
container.appendChild(wrapper); container.appendChild(wrapper);
...@@ -3255,65 +3285,6 @@ ...@@ -3255,65 +3285,6 @@
init = {}; init = {};
/**
* Try fetching data from JIO. Fallback to loading fake data until accessible
* @method fetchData
* @param {object} parcel Storage, query options and baggage to return
* @return {object} promise object/baggage
*/
// NOTE: until we have real data we load fake data on application init!
init.fetchData = function (parcel) {
return storage[parcel.storage].allDocs(parcel.query)
.then(function (response) {
return {
"response": response,
"baggage": parcel.baggage
};
});
};
/**
* parse a link into query-able parameters
* @method parseLink
* @param {string} url Url to go to
* @return {object} pointer object
*/
// TODO: renderJS should parse a link
init.parseLink = function (url) {
var i,
query,
parameter,
path = $.mobile.path.parseUrl(url),
clean_hash = path.hash.replace("#", ""),
config = {
"url": url
};
if (path.hash === "") {
config.id = config.layout_identifier = util.getActivePage();
} else {
// do we have a mode?
query = clean_hash.split("?");
for (i = 0; i < query.length; i += 1) {
parameter = query[i].split("=");
if (parameter.length === 2 && parameter[0] === "mode") {
config.mode = parameter[1];
}
}
config.fragment_list = clean_hash.split("::");
config.id = clean_hash;
config.layout_level = config.fragment_list.length - 1;
config.deeplink = true;
config.layout_identifier = clean_hash.split("::")[0];
}
return config;
};
/** /**
* Update info fields (field displaying some sort of information * Update info fields (field displaying some sort of information
* @method " * @method "
...@@ -3398,44 +3369,9 @@ ...@@ -3398,44 +3369,9 @@
} }
break; break;
} }
info_field.textContent = info;
}
};
/** info_field.textContent = info;
* Generate an action object (vs duplicate in every action call)
* @method generateActionObject
* @param {object} e Event triggering an action
* @return {object} action object
*/
// TODO: integrate in popup handler, make sure "pop" works!
// TODO: id ... is crap
init.generateActionObject = function (e) {
var element, pop, id, gadget;
switch (e.type) {
case "popupbeforeposition":
element = undefined;
id = e.target.id;
gadget = e.target;
break;
default:
element = e.target || e;
pop = element.getAttribute("data-rel") === null;
id = pop ?
(element.getAttribute("data-reference") || util.getActivePage()) :
(element.href === undefined ? util.getActivePage() :
element.href.split("#")[1]);
gadget = document.getElementById(id);
break;
} }
return {
"element": element,
"id": id,
"gadget": document.getElementById(id),
"state": gadget.state
};
}; };
/** /**
...@@ -3522,50 +3458,7 @@ ...@@ -3522,50 +3458,7 @@
); );
}; };
/**
* Action handler, routing actions to specified method
* @method action
* @param {object} e Event that triggered the action
*/
init.action = function (e) {
var type, tag, val, action, handler;
type = e.type;
tag = e.target.tagName;
if (type === "click" && e.target.getAttribute("data-rel") === null) {
e.preventDefault();
if (type === "click" && (tag === "SELECT" ||
(tag === "INPUT" && e.target.type !== "submit"))) {
return;
}
}
if (type === "change" && tag === "SELECT") {
val = e.target.options[e.target.selectedIndex].value;
}
// JQM bug on selects
// TODO: remove once fixed
if (tag === "SPAN" || tag === "OPTION") {
return;
}
// map
action = e.target.getAttribute("data-action");
handler = factory.map_actions[action];
if (action) {
if (handler) {
handler(init.generateActionObject(e), val);
} else {
util.errorHandler({
"Error": "Action: No method defined"
});
}
} else {
util.errorHandler({
"Error": "Action: No action defined for element"
});
}
};
/** /**
* Sort a selection of elements * Sort a selection of elements
...@@ -3639,7 +3532,11 @@ ...@@ -3639,7 +3532,11 @@
// give user half second to pick his state // give user half second to pick his state
app.timer = window.setTimeout(function () { app.timer = window.setTimeout(function () {
// update gadgets // update gadgets
app.setContent([{"children": [{"id": config.id}]}], {}, false); app.setContent(
{"generate":"gadget", "id": config.id, "href": config.id},
{},
false
);
app.timer = 0; app.timer = 0;
}, 500); }, 500);
}; };
...@@ -3672,7 +3569,7 @@ ...@@ -3672,7 +3569,7 @@
.then(function (reply) { .then(function (reply) {
// update state // update state
action_dict.state.query = init.generateQueryObject( action_dict.state.query = app.generateQueryObject(
{"query": action_dict.state.initial_query}, {"query": action_dict.state.initial_query},
reply.baggage.portal_type, reply.baggage.portal_type,
null, null,
...@@ -3681,178 +3578,59 @@ ...@@ -3681,178 +3578,59 @@
); );
// update gadget // update gadget
app.setContent([{"children": [{"id": action_dict.id}]}], {}, false); app.setContent(
{"generate":"gadget", "id": action_dict.id, "href": action_dict.id},
{},
false
);
}) })
.fail(util.errorHandler); .fail(util.errorHandler);
}; };
/** Determine if a property exists in an object
* @method findProbertyInObject
* @param {object} Object The object to search
* @param {string} Pointer The nested object pointer
* @param {string} Key The key under which it is stored
* @param {string} Key The value to search
* @return {boolean} true/null
*/
// TODO: Storage specific method, move
init.findKey = function (object, pointer, key, value) {
var i, obj;
// simple query
if (object[key] === value) {
return true;
}
if (object[pointer]) {
for (i = 0; i < object[pointer].length; i += 1) {
obj = object[pointer][i];
if (obj[key] === value) {
return true;
}
if (obj[pointer]) {
init.findKey(obj[pointer], pointer, key, value);
}
}
}
return null;
};
/** /**
* Build a query object based on passed configuration * Handler for form submission to add a new item
* @method generateQueryObject * @method add
* @param {object} config JSON parameters for query object * @param {object} config Action Object
* @param {string} type Portal Type to fetch
* @param {string} key Parameter to search single column
* @param {string} value Value to search for across one or all columns
* @param {object} field_list Items to search across
* @param {string} initial_query Intial query indicating blocked columns
* @return {object} query object
*/ */
// WARNING: complex_queries dependency! // NOTE: we always validate! to skip validation test for class on form
init.generateQueryObject = function (query, type, key, value, field_list) { // TODO: generate common promise handler for add/remove/clone/export/etc
var parameter, init.add = function (config) {
property, var property,
wrap, replace,
query_object, i,
query_clean, form_elements,
query_input = query || {}, form_element,
obj = {}, pass = false,
is_value = value && value !== ""; test_full,
test_empty,
obj.query = ''; test_time,
form_to_submit = document.getElementById(config.id),
anti_spam = document.getElementById(form_to_submit.id + "_not_a_secret"),
formData = new FormData(),
valid = $(form_to_submit).triggerHandler("submitForm");
// query string passed? parse it if (valid !== false) {
if (query_input.query) {
query_clean = query_input.query.replace(/'/g, '"');
query_object = complex_queries.QueryFactory.create(query_clean);
// missing portal type?
if (!init.findKey(query_object, "query_list", "key", "portal_type")) {
obj.query = '(portal_type:"%' + type + '" AND ';
}
// NOTE: json only allows single quotes inside double quotes. Reverse // spam - thx http://nedbatchelder.com/text/stopbots.html
// and add passed query to querystring if (anti_spam) {
obj.query += query_clean + ')';
} else {
obj.query = '(portal_type:"%' + type + '")';
}
// search "foo" = "bar" // fill form in < 1sec = spammer
if (is_value && key) { if (Date.now() - parseInt(anti_spam.getAttribute("data-created"))
obj.query += ' AND (' + key + ':"' + value + '")'; > 1000) {
obj.start = 0; test_time = true;
obj.items = 1; }
// search "bar" // one empty, one filled secured field
} else if (is_value) { form_elements = form_to_submit.getElementsByTagName("input");
// we need to check an existing query for the fields we are already for (i = 0; i < form_elements.length; i += 1) {
// searching. These fields should not be set in the search form_element = form_elements[i];
if (query && field_list) { if(util.testForClass(form_element.className, "secure_form")) {
wrap = ""; if (form_element.value === "") {
for (property in field_list) { test_empty = true;
if (field_list.hasOwnProperty(property)) { }
if (!init.findKey(query_object, "query_list", "key", property)) { if (form_element.value !== "") {
wrap += property + ':"%' + value + '%" OR '; test_full = true;
}
}
}
obj.query += ' AND (' + wrap.slice(0, -4) + ')';
}
}
if (query_input.sort_on) {
obj.sort_on = [query_input.sort];
} else {
obj.sort_on = [];
}
if (query_input.include_docs || value) {
obj.include_docs = true;
}
if (query_input.select_list && query_input.select_list.length > 0) {
obj.select_list = query_input.select_list;
}
if (query_input.limit) {
obj.start = query_input.limit[0];
obj.items = query_input.limit[1];
}
if (obj.start !== undefined) {
obj.limit = [obj.start, obj.items];
} else {
obj.limit = [];
}
if (query_input.wildcard_character) {
obj.wildcard_character = query_input.wildcard_character;
} else {
obj.wildcard_character = "%";
}
return obj;
};
/**
* Handler for form submission to add a new item
* @method add
* @param {object} config Action Object
*/
// NOTE: we always validate! to skip validation test for class on form
// TODO: generate common promise handler for add/remove/clone/export/etc
init.add = function (config) {
var property,
replace,
i,
form_elements,
form_element,
pass = false,
test_full,
test_empty,
test_time,
form_to_submit = document.getElementById(config.id),
anti_spam = document.getElementById(form_to_submit.id + "_not_a_secret"),
formData = new FormData(),
valid = $(form_to_submit).triggerHandler("submitForm");
if (valid !== false) {
// spam - thx http://nedbatchelder.com/text/stopbots.html
if (anti_spam) {
// fill form in < 2sec = spammer
if (Date.now() - parseInt(anti_spam.getAttribute("data-created"))
> 2000) {
test_time = true;
}
// one empty, one filled secured field
form_elements = form_to_submit.getElementsByTagName("input");
for (i = 0; i < form_elements.length; i += 1) {
form_element = form_elements[i];
if(util.testForClass(form_element.className, "secure_form")) {
if (form_element.value === "") {
test_empty = true;
}
if (form_element.value !== "") {
test_full = true;
} }
} }
} }
...@@ -3884,7 +3662,7 @@ ...@@ -3884,7 +3662,7 @@
"data": formData "data": formData
}) })
.then(function() { .then(function() {
$.mobile.changePage("#thanks") $.mobile.changePage("#thanks");
}) })
.fail(util.errorHandler); .fail(util.errorHandler);
} }
...@@ -3935,7 +3713,11 @@ ...@@ -3935,7 +3713,11 @@
config.state.query.limit = [start, records]; config.state.query.limit = [start, records];
// update gadget // update gadget
app.setContent([{"children": [{"id": config.id}]}], {}, false); app.setContent(
{"generate":"gadget", "id": config.id, "href": config.id},
{},
false
);
} else { } else {
util.errorHandler({ util.errorHandler({
"Error": "No state information stored for gadget" "Error": "No state information stored for gadget"
...@@ -3948,176 +3730,344 @@ ...@@ -3948,176 +3730,344 @@
} }
}; };
/* ====================================================================== */ /* ====================================================================== */
/* PAGE SETUP */ /* APP */
/* ====================================================================== */ /* ====================================================================== */
/*
/** * Object containing application related methods
* Update a page (which may already be in the DOM)
* @method updatePage
* @param {object} e Event (pagebeforechange)
* @param {object} data Data passed along with this (JQM) event
*/ */
// TODO: awful selector! app = {};
// NOTE: removed data for JSLINT form parameters
init.updatePage = function (e) {
var page = e.target;
app.fetchConfiguration({ /*
"storage": app.default_dict.storage_dict.settings, * Object containing default names, which can be overridden
"file": "pages", */
"attachment": page.id, app.default_dict = {
"baggage": undefined "storage_dict": {
}) "settings": "settings",
.then(function (reply) { "items": "items",
// TODO: bad, updating a page... based on state? "configuration": "configuration",
if (!page.querySelectorAll("div.ui-content")[0] "gadgets": "gagdets",
.getAttribute("data-bound")) { "data_type": "portal_types"
return app.setContent(reply, {"id": page.id}, undefined); },
} "path_dict": {
}) "data": "data",
.fail(util.errorHandler); "home": "home"
}
}; };
/** /**
* Set the page title * Timer for 500ms delayed actions
* @method setPageTitle
* @param {string} page_i18n Lookup value for title
*/ */
init.setPageTitle = function (page_i18n) { app.timer = 0;
var title,
value = factory.map_actions.translateLookup(page_i18n),
header = document.getElementById("global_header") ||
// WARNING: IE8- children() retrieves comments, too
document.getElementById(util.getActivePage()).children()[0];
if (util.testForClass(header.className, "ui-header")) { /**
title = header.getElementsByTagName("h1")[0]; * parse a link into query-able parameters
title.setAttribute("data-i18n", (page_i18n || "")); * @method parseLink
title.removeChild(title.childNodes[0]); * @param {string} url Url to go to
title.appendChild(document.createTextNode((value || "\u00A0"))); * @return {object} pointer object
*/
// TODO: renderJS should parse a link
app.parseLink = function (url) {
var i,
query,
parameter,
path = $.mobile.path.parseUrl(url),
clean_hash = path.hash.replace("#", ""),
config = {
"url": url
};
if (path.hash === "") {
config.id = config.layout_identifier = util.getActivePage();
} else {
// do we have a mode?
query = clean_hash.split("?");
for (i = 0; i < query.length; i += 1) {
parameter = query[i].split("=");
if (parameter.length === 2 && parameter[0] === "mode") {
config.mode = parameter[1];
}
}
config.fragment_list = clean_hash.split("::");
config.id = clean_hash;
config.layout_level = config.fragment_list.length - 1;
config.deeplink = true;
config.layout_identifier = clean_hash.split("::")[0];
} }
document.title = value;
return config;
}; };
/** /**
* Prevent filterable from triggering * Action handler, routing actions to specified method
* @method preventFilterableTrigger * @method action
* @params {object} element Filterable element * @param {object} e Event that triggered the action
*/ */
// TODO: make sure this triggers only once! app.action = function (e) {
init.preventFilterableTrigger = function (element) { var type, tag, val, action, handler;
$(element).on("filterablebeforefilter", function (e) {
type = e.type;
tag = e.target.tagName;
if (type === "click" && e.target.getAttribute("data-rel") === null) {
e.preventDefault(); e.preventDefault();
}); if (type === "click" && (tag === "SELECT" ||
(tag === "INPUT" && e.target.type !== "submit"))) {
return;
}
}
if (type === "change" && tag === "SELECT") {
val = e.target.options[e.target.selectedIndex].value;
}
// JQM bug on selects
// TODO: remove once fixed
if (tag === "SPAN" || tag === "OPTION") {
return;
}
// map
action = e.target.getAttribute("data-action");
handler = factory.map_actions[action];
if (action) {
if (handler) {
handler(app.generateActionObject(e), val);
} else {
util.errorHandler({
"Error": "Action: No method defined"
});
}
} else {
util.errorHandler({
"Error": "Action: No action defined for element"
});
}
}; };
/** /**
* Set bindings on page specific elements after content has been appended * Build a query object based on passed configuration
* @method setPageBindings * @method generateQueryObject
* @param {object} config JSON parameters for query object
* @param {string} type Portal Type to fetch
* @param {string} key Parameter to search single column
* @param {string} value Value to search for across one or all columns
* @param {object} field_list Items to search across
* @param {string} initial_query Intial query indicating blocked columns
* @return {object} query object
*/ */
// TODO: add local popups! // WARNING: complex_queries dependency!
init.setPageBindings = function () { app.generateQueryObject = function (query, type, key, value, field_list) {
var i, var parameter,
j, property,
form_element, wrap,
filterable, query_object,
captcha, query_clean,
result, query_input = query || {},
form_list = document.getElementsByTagName("form"), obj = {},
filter_list = document.querySelectorAll("[data-filter]"); is_value = value && value !== "";
// disable default filtering of JQM filterable
for (i = 0; i < filter_list.length; i += 1) {
filterable = filter_list[i];
if (filterable.getAttribute("data-bound") === null) { obj.query = '';
filterable.setAttribute("data-bound", true);
init.preventFilterableTrigger(filterable);
}
}
// add validation to all forms // query string passed? parse it
for (j = 0; j < form_list.length; j += 1) { if (query_input.query) {
form_element = form_list[j]; query_clean = query_input.query.replace(/'/g, '"');
captcha = document.getElementById(form_element.id + "_captcha"); query_object = complex_queries.QueryFactory.create(query_clean);
// captcha // missing portal type?
if (captcha) { if (!util.findKey(query_object, "query_list", "key", "portal_type")) {
util.declareJS( obj.query = '(portal_type: "%' + type + '" AND ';
"http://www.google.com/recaptcha/api/js/recaptcha_ajax.js"
).then(function(e) {
Recaptcha.create(
captcha.getAttribute("data-key"),
captcha.id,
{"theme": "red", "callback": Recaptcha.focus_response_field}
);
}).fail(util.errorHandler);
} }
if (form_element.getAttribute("data-bound") === null) { // NOTE: json only allows single quotes inside double quotes. Reverse
form_element.setAttribute("data-bound", true); // and add passed query to querystring
obj.query += query_clean + ')';
} else {
obj.query = '(portal_type: "%' + type + '")';
}
// TODO: javascript-able? // search "foo" = "bar"
// NOTE: the script is mapped to validval, so replacing it if (is_value && key) {
// requires it to add a different plugin here as well as obj.query += ' AND (' + key + ':"' + value + '")';
// updating all data-vv fields being set in mapFormField() and obj.start = 0;
// generateFormElement obj.items = 1;
$(form_element).validVal({
validate: { // search "bar"
onKeyup: "valid", } else if (is_value) {
onBlur: "valid" // we need to check an existing query for the fields we are already
}, // searching. These fields should not be set in the search
form: { if (query && field_list) {
onInvalid: util.return_out wrap = "";
for (property in field_list) {
if (field_list.hasOwnProperty(property)) {
if (!util.findKey(query_object, "query_list", "key", property)) {
wrap += property + ': "%' + value + '%" OR ';
}
} }
}); }
obj.query += ' AND (' + wrap.slice(0, -4) + ')';
} }
} }
};
if (query_input.sort_on) {
obj.sort_on = [query_input.sort];
} else {
obj.sort_on = [];
}
if (query_input.include_docs || value) {
obj.include_docs = true;
}
if (query_input.select_list && query_input.select_list.length > 0) {
obj.select_list = query_input.select_list;
}
if (query_input.limit) {
obj.start = query_input.limit[0];
obj.items = query_input.limit[1];
}
if (obj.start !== undefined) {
obj.limit = [obj.start, obj.items];
} else {
obj.limit = [];
}
if (query_input.wildcard_character) {
obj.wildcard_character = query_input.wildcard_character;
} else {
obj.wildcard_character = "%";
}
return obj;
};
/**
* Generate an action object (vs duplicate in every action call)
* @method generateActionObject
* @param {object} e Event triggering an action
* @return {object} action object
*/
// TODO: integrate in popup handler, make sure "pop" works!
// TODO: id ... is crap
app.generateActionObject = function (e) {
var element, pop, id, gadget;
switch (e.type) {
case "popupbeforeposition":
element = undefined;
id = e.target.id;
gadget = e.target;
break;
default:
element = e.target || e;
pop = element.getAttribute("data-rel") === null;
id = pop ?
(element.getAttribute("data-reference") || util.getActivePage()) :
(element.href === undefined ? util.getActivePage() :
element.href.split("#")[1]);
gadget = document.getElementById(id);
break;
}
return {
"element": element,
"id": id,
"gadget": document.getElementById(id),
"state": gadget.state
};
};
/**
* Make a breadcrumb url for easy navigation
* @method breadcrumb
* @param {object} url_dict Current URL object
* @return {object} HTML fragment
*/
app.breadcrumb = function (url_dict) {
var i,
crumb,
indicator,
breadcrumb = factory.element(
"span",
{"className": "crumbs"},
{"data-info": "url", "data-reference": url_dict.id}
),
makeLink = function (path, title, home) {
return factory.element(
"a",
{
"href": path || "#",
"className": "translate " + (home ?
"ui-btn ui-btn-icon-notext ui-icon-home" +
" ui-shadow ui-corner-all" : "ui-link")
},
{"data-enhanced": "true"},
{
"text": home ? "Home" : (title || ""),
"data-i18n": home ? "pages.home.title" :
(title ? "pages." + title.split("?")[0] + ".title" : null)
}
);
};
// home
breadcrumb.appendChild(
makeLink(app.default_dict.path_dict.home, undefined, true)
);
// fragments
for (i = 0; i < url_dict.fragment_list.length; i += 1) {
crumb = url_dict.fragment_list[i];
indicator = i === 0 ? "#" : "::";
breadcrumb.appendChild(document.createTextNode("|\u00A0"));
breadcrumb.appendChild(
makeLink(indicator + crumb, crumb)
);
breadcrumb.appendChild(document.createTextNode("\u00A0"));
}
// translate
if (i18n) {
factory.map_actions.translateNodeList(breadcrumb);
}
return breadcrumb;
};
/* ====================================================================== */ /**
/* APP */ * Set the page title
/* ====================================================================== */ * @method setPageTitle
/* * @param {string} page_i18n Lookup value for title
* Object containing application related methods * @param {object} page_header Header, in case it's still being generated
*/ */
app = {}; app.setPageTitle = function (page_i18n, page_header) {
var title,
value = factory.map_actions.translateLookup(page_i18n),
header = page_header.length ? page_header[0] :
(document.getElementById("global-header") ||
// WARNING: IE8- children() retrieves comments, too
document.getElementById(util.getActivePage()).children[0]);
/* if (util.testForClass(header.className, "ui-header")) {
* Object containing default names, which can be overridden title = header.getElementsByTagName("h1")[0];
*/ title.setAttribute("data-i18n", (page_i18n || ""));
app.default_dict = { title.removeChild(title.childNodes[0]);
"storage_dict": { title.appendChild(document.createTextNode((value || "\u00A0")));
"settings": "settings",
"items": "items",
"configuration": "configuration",
"gadgets": "gagdets",
"data_type": "portal_types"
},
"path_dict": {
"data": "data"
} }
document.title = value;
}; };
/**
* Timer for 500ms delayed actions
*/
app.timer = 0;
/** /**
* Create/update elements * Create/update elements
* @method setupPageElements * @method setupPageElements
...@@ -4128,7 +4078,6 @@ ...@@ -4128,7 +4078,6 @@
app.setContent = function (content_dict, url_dict, create) { app.setContent = function (content_dict, url_dict, create) {
var i, skip, container, target, baggage, spec; var i, skip, container, target, baggage, spec;
// TODO: make sure 3rd parameter does not interfere in any widget method!
// TODO: make sure formElement parameters can always be set like this // TODO: make sure formElement parameters can always be set like this
// when generating a form, all parameters for formElement are used, when // when generating a form, all parameters for formElement are used, when
// doing it from here, they are not! // doing it from here, they are not!
...@@ -4154,9 +4103,8 @@ ...@@ -4154,9 +4103,8 @@
}; };
} else { } else {
// prepare to fetch dynamic data // prepare to fetch dynamic data
// TODO: check if still needed to pass all page related info
baggage = { baggage = {
"title": content_dict.title || "",
"title_i18n": content_dict.title_i18n || "",
"mode": spec.mode || null, "mode": spec.mode || null,
"create": create, "create": create,
"layout_level": spec.layout_level || null, "layout_level": spec.layout_level || null,
...@@ -4179,7 +4127,7 @@ ...@@ -4179,7 +4127,7 @@
"attachment": baggage.id || app.default_dict.storage_dict.configuration, "attachment": baggage.id || app.default_dict.storage_dict.configuration,
"baggage": baggage "baggage": baggage
}) })
.then(app.parseGadgetConfiguration) .then(app.parseConfiguration)
// TODO: remove once working with live data // TODO: remove once working with live data
// ===== SAMPLE DATA ======== // ===== SAMPLE DATA ========
.then(app.testStorageForData) .then(app.testStorageForData)
...@@ -4253,7 +4201,6 @@ ...@@ -4253,7 +4201,6 @@
} }
}; };
/** /**
* Generate gadget content based on query data and passed config * Generate gadget content based on query data and passed config
* @method generateGadgetContent * @method generateGadgetContent
...@@ -4285,11 +4232,6 @@ ...@@ -4285,11 +4232,6 @@
(baggage.create === false ? true : null) (baggage.create === false ? true : null)
); );
// set header
if (baggage.create) {
init.setPageTitle(baggage.title_i18n);
}
// translate // translate
factory.map_actions.translateNodeList(element); factory.map_actions.translateNodeList(element);
...@@ -4309,6 +4251,7 @@ ...@@ -4309,6 +4251,7 @@
baggage.state.constructor = baggage.constructor; baggage.state.constructor = baggage.constructor;
baggage.state.selected = baggage.create === false ? baggage.state.selected = baggage.create === false ?
(baggage.state.selected) : undefined; (baggage.state.selected) : undefined;
// WARNING: this should use data(), it is bad practice to store like this
selector.state = baggage.state; selector.state = baggage.state;
init.updateInfoFields( init.updateInfoFields(
...@@ -4333,6 +4276,7 @@ ...@@ -4333,6 +4276,7 @@
if (baggage.skip === undefined) { if (baggage.skip === undefined) {
// single item query // single item query
// TODO: more levels? how to generalize and not only search by _id? // TODO: more levels? how to generalize and not only search by _id?
// TODO: if this is just setting content_dict? then make it work without!
if (baggage.layout_level > 0) { if (baggage.layout_level > 0) {
baggage.value = baggage.fragments[baggage.layout_level]; baggage.value = baggage.fragments[baggage.layout_level];
} }
...@@ -4344,7 +4288,7 @@ ...@@ -4344,7 +4288,7 @@
baggage.state.query.limit = baggage.store_limit; baggage.state.query.limit = baggage.store_limit;
delete baggage.store_limit; delete baggage.store_limit;
} else { } else {
baggage.state.query = init.generateQueryObject( baggage.state.query = app.generateQueryObject(
baggage.config.initial_query, baggage.config.initial_query,
baggage.type, baggage.type,
'_id', '_id',
...@@ -4354,7 +4298,7 @@ ...@@ -4354,7 +4298,7 @@
// get an item? // get an item?
if (baggage.mode !== "new" || baggage.config.initial_query !== undefined) { if (baggage.mode !== "new" || baggage.config.initial_query !== undefined) {
return init.fetchData({ return app.fetchData({
"storage": "items", "storage": "items",
"query": baggage.state.query, "query": baggage.state.query,
"baggage": baggage "baggage": baggage
...@@ -4385,16 +4329,17 @@ ...@@ -4385,16 +4329,17 @@
baggage.state = {}; baggage.state = {};
baggage.state.method = baggage.constructor; baggage.state.method = baggage.constructor;
if (baggage.config.initial_query) { if (baggage.config.initial_query) {
baggage.state.query = init.generateQueryObject( baggage.state.query = app.generateQueryObject(
{"query": baggage.config.initial_query.query}, {"query": baggage.config.initial_query.query},
baggage.type baggage.type
); );
} }
} }
// skip total for single item layouts! // skip total for single item layouts!
if (baggage.config.initial_query) { if (baggage.config.initial_query) {
baggage.state.initial_query = baggage.config.initial_query.query; baggage.state.initial_query = baggage.config.initial_query.query;
return init.fetchData({ return app.fetchData({
"baggage": baggage, "baggage": baggage,
"storage": "items", "storage": "items",
"query": baggage.state.query "query": baggage.state.query
...@@ -4505,9 +4450,9 @@ ...@@ -4505,9 +4450,9 @@
// try to get 1 record // try to get 1 record
if (baggage.create !== false && baggage.config.initial_query) { if (baggage.create !== false && baggage.config.initial_query) {
return init.fetchData({ return app.fetchData({
"storage": "items", "storage": "items",
"query": init.generateQueryObject({"limit": [0, 1]}, baggage.type), "query": app.generateQueryObject({"limit": [0, 1]}, baggage.type),
"baggage": baggage "baggage": baggage
}); });
} }
...@@ -4519,11 +4464,11 @@ ...@@ -4519,11 +4464,11 @@
// ======================== SAMPLE DATA ================================== // ======================== SAMPLE DATA ==================================
/** /**
* parses a gadget configuration file * parses a gadget configuration file
* @method parseGadgetConfiguration * @method parseConfiguration
* @param {object} reply gadget configuration * @param {object} reply gadget configuration
* @return {object} response object/promise * @return {object} response object/promise
*/ */
app.parseGadgetConfiguration = function (reply) { app.parseConfiguration = function (reply) {
var parsed, baggage = reply.baggage; var parsed, baggage = reply.baggage;
if (baggage.skip === undefined) { if (baggage.skip === undefined) {
...@@ -4600,31 +4545,20 @@ ...@@ -4600,31 +4545,20 @@
}; };
/** /**
* Store a configuration file in storage * Try fetching data from JIO. Fallback to loading fake data until accessible
* @method storeConfigurationFile * @method fetchData
* @param {object} e Response event * @param {object} parcel Storage, query options and baggage to return
* @return {object} promise object * @return {object} promise object/baggage
*/ */
// NOTE: configuration is stored as attachment // NOTE: until we have real data we load fake data on application init!
app.storeConfigurationFile = function (response) { app.fetchData = function (parcel) {
var storage_location = property_dict.store || return storage[parcel.storage].allDocs(parcel.query)
(storage ? storage[app.default_dict.storage_dict.settings] : undefined); .then(function (response) {
return {
if (storage_location === undefined) { "response": response,
util.errorHandler({"error": "getFromDisk: no storage defined"}); "baggage": parcel.baggage
return RSVP.all([]); };
}
return storage_location.put(
{"_id": (property_dict.file || app.default_dict.storage_dict.settings)}
).then(function () {
return storage_location.putAttachment({
"_id": (property_dict.file || app.default_dict.storage_dict.settings),
"_attachment": (property_dict.attachment || app.default_dict.storage_dict.configuration),
"_data": JSON.stringify(response),
"_mimetype": "application/json"
}); });
})
.fail(util.errorHandler);
}; };
/** /**
...@@ -4714,7 +4648,7 @@ ...@@ -4714,7 +4648,7 @@
} }
if (typeof raw_url === "string") { if (typeof raw_url === "string") {
config = init.parseLink(raw_url); config = app.parseLink(raw_url);
if (e) { if (e) {
if (document.getElementById(raw_url.split("#").pop()) || if (document.getElementById(raw_url.split("#").pop()) ||
...@@ -4730,7 +4664,7 @@ ...@@ -4730,7 +4664,7 @@
e.preventDefault(); e.preventDefault();
} else { } else {
// HACK: overwrite JQM history, so deeplinks are correctly handled // overwrite JQM history, so deeplinks are correctly handled
if ($.mobile.navigate.history.initialDst && if ($.mobile.navigate.history.initialDst &&
window.location.hash !== "") { window.location.hash !== "") {
...@@ -4768,11 +4702,42 @@ ...@@ -4768,11 +4702,42 @@
.then(function (reply) { .then(function (reply) {
return app.setContent(reply, config, create); return app.setContent(reply, config, create);
}) })
.then(init.setPageBindings) .then(app.setPageBindings)
.fail(util.errorHandler); .fail(util.errorHandler);
} }
}; };
/**
* Update a page (which may already be in the DOM)
* @method updatePage
* @param {object} e Event (pagebeforechange)
* @param {object} data Data passed along with this (JQM) event
*/
// TODO: awful selector!
// NOTE: removed data for JSLINT form parameters
app.updatePage = function (e) {
var page = e.target;
app.fetchConfiguration({
"storage": app.default_dict.storage_dict.settings,
"file": "pages",
"attachment": page.id,
"baggage": undefined
})
.then(function (reply) {
// TODO: bad, updating a page... based on state?
if (!page.querySelectorAll("div.ui-content")[0]
.getAttribute("data-bound")) {
return app.setContent(
reply,
{"id": page.id, "href": window.location.href},
undefined
);
}
})
.fail(util.errorHandler);
};
/** /**
* Set global bindings for all application elements * Set global bindings for all application elements
* @method setGlobalBindings * @method setGlobalBindings
...@@ -4790,7 +4755,7 @@ ...@@ -4790,7 +4755,7 @@
// update // update
.on("pagebeforeshow", "div.ui-page", function (e, data) { .on("pagebeforeshow", "div.ui-page", function (e, data) {
init.updatePage(e, data); app.updatePage(e, data);
}) })
// clean dynamic pages on hide // clean dynamic pages on hide
...@@ -4820,7 +4785,7 @@ ...@@ -4820,7 +4785,7 @@
case "button": case "button":
case "checkbox": case "checkbox":
case "reset": case "reset":
init.action(e); app.action(e);
break; break;
default: default:
val = element.value; val = element.value;
...@@ -4836,23 +4801,100 @@ ...@@ -4836,23 +4801,100 @@
// give user half second to pick his state // give user half second to pick his state
app.timer = window.setTimeout(function () { app.timer = window.setTimeout(function () {
element.setAttribute("data-last", val); element.setAttribute("data-last", val);
init.action(e); app.action(e);
app.timer = 0; app.timer = 0;
}, 500); }, 500);
break; break;
} }
} else { } else {
init.action(e); app.action(e);
} }
}) })
// popup content loading // popup content loading
.find("#global_popup") .find("#global_popup")
.on("popupbeforeposition", function (e) { .on("popupbeforeposition", function (e) {
factory.util.loadPopupContents(init.generateActionObject(e)); factory.util.loadPopupContents(app.generateActionObject(e));
}); });
}; };
/**
* Prevent filterable from triggering
* @method preventFilterableTrigger
* @params {object} element Filterable element
*/
// TODO: make sure this triggers only once!
app.preventFilterableTrigger = function (element) {
$(element).on("filterablebeforefilter", function (e) {
e.preventDefault();
});
};
/**
* Set bindings on page specific elements after content has been appended
* @method setPageBindings
*/
// TODO: add local popups!
app.setPageBindings = function () {
var i,
j,
form_element,
filterable,
captcha,
result,
form_list = document.getElementsByTagName("form"),
filter_list = document.querySelectorAll("[data-filter]");
// disable default filtering of JQM filterable
// TODO: does not work! JQM still runs
for (i = 0; i < filter_list.length; i += 1) {
filterable = filter_list[i];
if (filterable.getAttribute("data-bound") === null) {
filterable.setAttribute("data-bound", true);
app.preventFilterableTrigger(filterable);
}
}
// add validation to all forms
for (j = 0; j < form_list.length; j += 1) {
form_element = form_list[j];
captcha = document.getElementById(form_element.id + "_captcha");
// captcha
if (captcha) {
util.declareJS(
"http://www.google.com/recaptcha/api/js/recaptcha_ajax.js"
).then(function(e) {
Recaptcha.create(
captcha.getAttribute("data-key"),
captcha.id,
{"theme": "red", "callback": Recaptcha.focus_response_field}
);
}).fail(util.errorHandler);
}
if (form_element.getAttribute("data-bound") === null) {
form_element.setAttribute("data-bound", true);
// TODO: javascript-able?
// NOTE: the script is mapped to validval, so replacing it
// requires it to add a different plugin here as well as
// updating all data-vv fields being set in mapFormField() and
// generateFormElement
$(form_element).validVal({
validate: {
onKeyup: "valid",
onBlur: "valid"
},
form: {
onInvalid: util.return_out
}
});
}
}
};
/* ====================================================================== */ /* ====================================================================== */
/* UTILS */ /* UTILS */
/* ====================================================================== */ /* ====================================================================== */
...@@ -4921,6 +4963,63 @@ ...@@ -4921,6 +4963,63 @@
return undefined; return undefined;
}; };
/**
* Show and hide the jQuery Mobile loader passing a status message
* @method loader
* @param {boolean} show Whether to show or hide the loader
* @param {message} message The message to display in the loader
* @param {icon} icon Which icon to display when overriding the loader
*/
util.showStatus = function (show, message, icon) {
// hm... jQuery...
if (app.default_dict.loader) {
$.mobile.loading(
show ? "show" : "hide",
{
"theme": app.default_dict.loader_theme,
"text": icon === undefined ? message : "",
"html": icon === undefined ? "" :
'<span class="ui-icon-' + icon +
' loader_icon> </span><h1 class="loader_message">' +
message + '</h1>'
}
);
} else {
util.errorHandler({"error": "showStatus: Loader not enabled"});
}
};
/** Determine if a property exists in an object
* @method findKey
* @param {object} Object The object to search
* @param {string} Pointer The nested object pointer
* @param {string} Key The key under which it is stored
* @param {string} Key The value to search
* @return {boolean} true/null
*/
// TODO: Storage specific method, move
util.findKey = function (object, pointer, key, value) {
var i, obj;
// simple query
if (object[key] === value) {
return true;
}
if (object[pointer]) {
for (i = 0; i < object[pointer].length; i += 1) {
obj = object[pointer][i];
if (obj[key] === value) {
return true;
}
if (obj[pointer]) {
util.findKey(obj[pointer], pointer, key, value);
}
}
}
return null;
};
/** /**
* Promise-optimized Ajax (same as used in JIO, thx * Promise-optimized Ajax (same as used in JIO, thx
* http://git.erp5.org/gitweb/jio.git/blob_plain/refs/heads/master:/jio.js * http://git.erp5.org/gitweb/jio.git/blob_plain/refs/heads/master:/jio.js
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment