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

added breadcrumbs, fixed infoFields and broken deeplink

parent 7a48da05
......@@ -658,15 +658,26 @@
content,
container,
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;
// 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
// TODO: should this be optional?
if (path) {
for (single_path in path) {
if (path.hasOwnProperty(single_path)) {
app.default_dict.path_dict[single_path] = path[single_path];
if (path_flag) {
for (single_path in path_flag) {
if (path_flag.hasOwnProperty(single_path)) {
app.default_dict.path_dict[single_path] = path_flag[single_path];
}
}
} else {
......@@ -795,7 +806,16 @@
if (page_dict === undefined) {
util.errorHandler({"error":"Pageindex: Missing page definition"});
} 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;
},
......@@ -2372,6 +2392,7 @@
factory.page = function (content_dict, url_dict, create) {
var i,
j,
k,
last,
element,
wrapper,
......@@ -2382,7 +2403,8 @@
if (content_dict.children) {
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 @@
}
);
// 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);
container.appendChild(wrapper);
......@@ -3255,65 +3285,6 @@
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
* @method "
......@@ -3398,44 +3369,9 @@
}
break;
}
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;
info_field.textContent = info;
}
return {
"element": element,
"id": id,
"gadget": document.getElementById(id),
"state": gadget.state
};
};
/**
......@@ -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
......@@ -3639,7 +3532,11 @@
// give user half second to pick his state
app.timer = window.setTimeout(function () {
// update gadgets
app.setContent([{"children": [{"id": config.id}]}], {}, false);
app.setContent(
{"generate":"gadget", "id": config.id, "href": config.id},
{},
false
);
app.timer = 0;
}, 500);
};
......@@ -3672,7 +3569,7 @@
.then(function (reply) {
// update state
action_dict.state.query = init.generateQueryObject(
action_dict.state.query = app.generateQueryObject(
{"query": action_dict.state.initial_query},
reply.baggage.portal_type,
null,
......@@ -3681,178 +3578,59 @@
);
// 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);
};
/** 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
* @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
* Handler for form submission to add a new item
* @method add
* @param {object} config Action Object
*/
// WARNING: complex_queries dependency!
init.generateQueryObject = function (query, type, key, value, field_list) {
var parameter,
property,
wrap,
query_object,
query_clean,
query_input = query || {},
obj = {},
is_value = value && value !== "";
obj.query = '';
// 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");
// query string passed? parse it
if (query_input.query) {
query_clean = query_input.query.replace(/'/g, '"');
query_object = complex_queries.QueryFactory.create(query_clean);
if (valid !== false) {
// 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
// and add passed query to querystring
obj.query += query_clean + ')';
} else {
obj.query = '(portal_type:"%' + type + '")';
}
// spam - thx http://nedbatchelder.com/text/stopbots.html
if (anti_spam) {
// search "foo" = "bar"
if (is_value && key) {
obj.query += ' AND (' + key + ':"' + value + '")';
obj.start = 0;
obj.items = 1;
// fill form in < 1sec = spammer
if (Date.now() - parseInt(anti_spam.getAttribute("data-created"))
> 1000) {
test_time = true;
}
// search "bar"
} else if (is_value) {
// we need to check an existing query for the fields we are already
// searching. These fields should not be set in the search
if (query && field_list) {
wrap = "";
for (property in field_list) {
if (field_list.hasOwnProperty(property)) {
if (!init.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;
};
/**
* 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;
// 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 @@
"data": formData
})
.then(function() {
$.mobile.changePage("#thanks")
$.mobile.changePage("#thanks");
})
.fail(util.errorHandler);
}
......@@ -3935,7 +3713,11 @@
config.state.query.limit = [start, records];
// update gadget
app.setContent([{"children": [{"id": config.id}]}], {}, false);
app.setContent(
{"generate":"gadget", "id": config.id, "href": config.id},
{},
false
);
} else {
util.errorHandler({
"Error": "No state information stored for gadget"
......@@ -3948,176 +3730,344 @@
}
};
/* ====================================================================== */
/* PAGE SETUP */
/* APP */
/* ====================================================================== */
/**
* 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
/*
* Object containing application related methods
*/
// TODO: awful selector!
// NOTE: removed data for JSLINT form parameters
init.updatePage = function (e) {
var page = e.target;
app = {};
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}, undefined);
}
})
.fail(util.errorHandler);
/*
* Object containing default names, which can be overridden
*/
app.default_dict = {
"storage_dict": {
"settings": "settings",
"items": "items",
"configuration": "configuration",
"gadgets": "gagdets",
"data_type": "portal_types"
},
"path_dict": {
"data": "data",
"home": "home"
}
};
/**
* Set the page title
* @method setPageTitle
* @param {string} page_i18n Lookup value for title
/**
* Timer for 500ms delayed actions
*/
init.setPageTitle = function (page_i18n) {
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];
app.timer = 0;
if (util.testForClass(header.className, "ui-header")) {
title = header.getElementsByTagName("h1")[0];
title.setAttribute("data-i18n", (page_i18n || ""));
title.removeChild(title.childNodes[0]);
title.appendChild(document.createTextNode((value || "\u00A0")));
/**
* 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
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
* @method preventFilterableTrigger
* @params {object} element Filterable element
* Action handler, routing actions to specified method
* @method action
* @param {object} e Event that triggered the action
*/
// TODO: make sure this triggers only once!
init.preventFilterableTrigger = function (element) {
$(element).on("filterablebeforefilter", function (e) {
app.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(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
* @method setPageBindings
* Build a query object based on passed configuration
* @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!
init.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
for (i = 0; i < filter_list.length; i += 1) {
filterable = filter_list[i];
// WARNING: complex_queries dependency!
app.generateQueryObject = function (query, type, key, value, field_list) {
var parameter,
property,
wrap,
query_object,
query_clean,
query_input = query || {},
obj = {},
is_value = value && value !== "";
if (filterable.getAttribute("data-bound") === null) {
filterable.setAttribute("data-bound", true);
init.preventFilterableTrigger(filterable);
}
}
obj.query = '';
// 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");
// query string passed? parse it
if (query_input.query) {
query_clean = query_input.query.replace(/'/g, '"');
query_object = complex_queries.QueryFactory.create(query_clean);
// 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);
// missing portal type?
if (!util.findKey(query_object, "query_list", "key", "portal_type")) {
obj.query = '(portal_type: "%' + type + '" AND ';
}
if (form_element.getAttribute("data-bound") === null) {
form_element.setAttribute("data-bound", true);
// NOTE: json only allows single quotes inside double quotes. Reverse
// and add passed query to querystring
obj.query += query_clean + ')';
} else {
obj.query = '(portal_type: "%' + type + '")';
}
// 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
// search "foo" = "bar"
if (is_value && key) {
obj.query += ' AND (' + key + ':"' + value + '")';
obj.start = 0;
obj.items = 1;
// search "bar"
} else if (is_value) {
// we need to check an existing query for the fields we are already
// searching. These fields should not be set in the search
if (query && field_list) {
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 */
/* ====================================================================== */
/*
* Object containing application related methods
/**
* Set the page title
* @method setPageTitle
* @param {string} page_i18n Lookup value for title
* @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]);
/*
* Object containing default names, which can be overridden
*/
app.default_dict = {
"storage_dict": {
"settings": "settings",
"items": "items",
"configuration": "configuration",
"gadgets": "gagdets",
"data_type": "portal_types"
},
"path_dict": {
"data": "data"
if (util.testForClass(header.className, "ui-header")) {
title = header.getElementsByTagName("h1")[0];
title.setAttribute("data-i18n", (page_i18n || ""));
title.removeChild(title.childNodes[0]);
title.appendChild(document.createTextNode((value || "\u00A0")));
}
document.title = value;
};
/**
* Timer for 500ms delayed actions
*/
app.timer = 0;
/**
* Create/update elements
* @method setupPageElements
......@@ -4128,7 +4078,6 @@
app.setContent = function (content_dict, url_dict, create) {
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
// when generating a form, all parameters for formElement are used, when
// doing it from here, they are not!
......@@ -4154,9 +4103,8 @@
};
} else {
// prepare to fetch dynamic data
// TODO: check if still needed to pass all page related info
baggage = {
"title": content_dict.title || "",
"title_i18n": content_dict.title_i18n || "",
"mode": spec.mode || null,
"create": create,
"layout_level": spec.layout_level || null,
......@@ -4179,7 +4127,7 @@
"attachment": baggage.id || app.default_dict.storage_dict.configuration,
"baggage": baggage
})
.then(app.parseGadgetConfiguration)
.then(app.parseConfiguration)
// TODO: remove once working with live data
// ===== SAMPLE DATA ========
.then(app.testStorageForData)
......@@ -4253,7 +4201,6 @@
}
};
/**
* Generate gadget content based on query data and passed config
* @method generateGadgetContent
......@@ -4285,11 +4232,6 @@
(baggage.create === false ? true : null)
);
// set header
if (baggage.create) {
init.setPageTitle(baggage.title_i18n);
}
// translate
factory.map_actions.translateNodeList(element);
......@@ -4309,6 +4251,7 @@
baggage.state.constructor = baggage.constructor;
baggage.state.selected = baggage.create === false ?
(baggage.state.selected) : undefined;
// WARNING: this should use data(), it is bad practice to store like this
selector.state = baggage.state;
init.updateInfoFields(
......@@ -4333,6 +4276,7 @@
if (baggage.skip === undefined) {
// single item query
// 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) {
baggage.value = baggage.fragments[baggage.layout_level];
}
......@@ -4344,7 +4288,7 @@
baggage.state.query.limit = baggage.store_limit;
delete baggage.store_limit;
} else {
baggage.state.query = init.generateQueryObject(
baggage.state.query = app.generateQueryObject(
baggage.config.initial_query,
baggage.type,
'_id',
......@@ -4354,7 +4298,7 @@
// get an item?
if (baggage.mode !== "new" || baggage.config.initial_query !== undefined) {
return init.fetchData({
return app.fetchData({
"storage": "items",
"query": baggage.state.query,
"baggage": baggage
......@@ -4385,16 +4329,17 @@
baggage.state = {};
baggage.state.method = baggage.constructor;
if (baggage.config.initial_query) {
baggage.state.query = init.generateQueryObject(
baggage.state.query = app.generateQueryObject(
{"query": baggage.config.initial_query.query},
baggage.type
);
}
}
// skip total for single item layouts!
if (baggage.config.initial_query) {
baggage.state.initial_query = baggage.config.initial_query.query;
return init.fetchData({
return app.fetchData({
"baggage": baggage,
"storage": "items",
"query": baggage.state.query
......@@ -4505,9 +4450,9 @@
// try to get 1 record
if (baggage.create !== false && baggage.config.initial_query) {
return init.fetchData({
return app.fetchData({
"storage": "items",
"query": init.generateQueryObject({"limit": [0, 1]}, baggage.type),
"query": app.generateQueryObject({"limit": [0, 1]}, baggage.type),
"baggage": baggage
});
}
......@@ -4519,11 +4464,11 @@
// ======================== SAMPLE DATA ==================================
/**
* parses a gadget configuration file
* @method parseGadgetConfiguration
* @method parseConfiguration
* @param {object} reply gadget configuration
* @return {object} response object/promise
*/
app.parseGadgetConfiguration = function (reply) {
app.parseConfiguration = function (reply) {
var parsed, baggage = reply.baggage;
if (baggage.skip === undefined) {
......@@ -4600,31 +4545,20 @@
};
/**
* Store a configuration file in storage
* @method storeConfigurationFile
* @param {object} e Response event
* @return {object} promise object
* 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: configuration is stored as attachment
app.storeConfigurationFile = function (response) {
var storage_location = property_dict.store ||
(storage ? storage[app.default_dict.storage_dict.settings] : undefined);
if (storage_location === undefined) {
util.errorHandler({"error": "getFromDisk: no storage defined"});
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"
// NOTE: until we have real data we load fake data on application init!
app.fetchData = function (parcel) {
return storage[parcel.storage].allDocs(parcel.query)
.then(function (response) {
return {
"response": response,
"baggage": parcel.baggage
};
});
})
.fail(util.errorHandler);
};
/**
......@@ -4714,7 +4648,7 @@
}
if (typeof raw_url === "string") {
config = init.parseLink(raw_url);
config = app.parseLink(raw_url);
if (e) {
if (document.getElementById(raw_url.split("#").pop()) ||
......@@ -4730,7 +4664,7 @@
e.preventDefault();
} else {
// HACK: overwrite JQM history, so deeplinks are correctly handled
// overwrite JQM history, so deeplinks are correctly handled
if ($.mobile.navigate.history.initialDst &&
window.location.hash !== "") {
......@@ -4768,11 +4702,42 @@
.then(function (reply) {
return app.setContent(reply, config, create);
})
.then(init.setPageBindings)
.then(app.setPageBindings)
.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
* @method setGlobalBindings
......@@ -4790,7 +4755,7 @@
// update
.on("pagebeforeshow", "div.ui-page", function (e, data) {
init.updatePage(e, data);
app.updatePage(e, data);
})
// clean dynamic pages on hide
......@@ -4820,7 +4785,7 @@
case "button":
case "checkbox":
case "reset":
init.action(e);
app.action(e);
break;
default:
val = element.value;
......@@ -4836,23 +4801,100 @@
// give user half second to pick his state
app.timer = window.setTimeout(function () {
element.setAttribute("data-last", val);
init.action(e);
app.action(e);
app.timer = 0;
}, 500);
break;
}
} else {
init.action(e);
app.action(e);
}
})
// popup content loading
.find("#global_popup")
.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 */
/* ====================================================================== */
......@@ -4921,6 +4963,63 @@
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
* 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