Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
E
ecommerce-ui
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
ecommerce-ui
Commits
048f69a4
Commit
048f69a4
authored
Nov 29, 2013
by
Sven Franck
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
added breadcrumbs, fixed infoFields and broken deeplink
parent
7a48da05
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
588 additions
and
489 deletions
+588
-489
js/erp5_loader.js
js/erp5_loader.js
+588
-489
No files found.
js/erp5_loader.js
View file @
048f69a4
...
...
@@ -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
* @param
s {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
.
parse
Gadget
Configuration
)
.
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 parse
Gadget
Configuration
* @method parseConfiguration
* @param {object} reply gadget configuration
* @return {object} response object/promise
*/
app
.
parse
Gadget
Configuration
=
function
(
reply
)
{
app
.
parseConfiguration
=
function
(
reply
)
{
var
parsed
,
baggage
=
reply
.
baggage
;
if
(
baggage
.
skip
===
undefined
)
{
...
...
@@ -4600,31 +4545,20 @@
};
/**
*
Store a configuration file in storag
e
* @method
storeConfigurationFile
* @param {object}
e Response event
* @return {object} promise object
*
Try fetching data from JIO. Fallback to loading fake data until accessibl
e
* @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
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment