Commit e37a7e68 authored by Romain Courteaud's avatar Romain Courteaud

Implement declareGadget.

Add an example of renderjs usage.
parent 3fbd6e26
...@@ -14,7 +14,11 @@ all: external lint test build doc ...@@ -14,7 +14,11 @@ all: external lint test build doc
external: lib/sinon/sinon.js \ external: lib/sinon/sinon.js \
lib/jquery/jquery.js \ lib/jquery/jquery.js \
lib/qunit/qunit.js \ lib/qunit/qunit.js \
lib/qunit/qunit.css lib/qunit/qunit.css \
lib/jio/jio.js \
lib/jio/md5.js \
lib/jio/complex_queries.js \
lib/jio/localstorage.js
lib/sinon/sinon.js: lib/sinon/sinon.js:
@mkdir -p $(@D) @mkdir -p $(@D)
...@@ -22,12 +26,28 @@ lib/sinon/sinon.js: ...@@ -22,12 +26,28 @@ lib/sinon/sinon.js:
lib/jquery/jquery.js: lib/jquery/jquery.js:
@mkdir -p $(@D) @mkdir -p $(@D)
curl -s -o $@ http://code.jquery.com/jquery-2.0.3.js curl -s -o $@ http://code.jquery.com/jquery-1.9.1.js
lib/qunit/qunit.%: lib/qunit/qunit.%:
@mkdir -p $(@D) @mkdir -p $(@D)
curl -s -o $@ http://code.jquery.com/qunit/qunit-1.12.0$(suffix $@) curl -s -o $@ http://code.jquery.com/qunit/qunit-1.12.0$(suffix $@)
lib/jio/jio.js:
@mkdir -p $(@D)
curl -s -o $@ http://git.erp5.org/gitweb/jio.git/blob_plain/refs/heads/master:/jio.js
lib/jio/md5.js:
@mkdir -p $(@D)
curl -s -o $@ http://git.erp5.org/gitweb/jio.git/blob_plain/refs/heads/master:/lib/md5/md5.js
lib/jio/localstorage.js:
@mkdir -p $(@D)
curl -s -o $@ http://git.erp5.org/gitweb/jio.git/blob_plain/refs/heads/master:/src/jio.storage/localstorage.js
lib/jio/complex_queries.js:
@mkdir -p $(@D)
curl -s -o $@ http://git.erp5.org/gitweb/jio.git/blob_plain/refs/heads/master:/complex_queries.js
$(RENDERJS_MIN): $(RENDERJS) $(RENDERJS_MIN): $(RENDERJS)
$(UGLIFY_CMD) "$<" > "$@" $(UGLIFY_CMD) "$<" > "$@"
...@@ -49,4 +69,4 @@ lint: ${BUILDDIR}/$(RENDERJS).lint ...@@ -49,4 +69,4 @@ lint: ${BUILDDIR}/$(RENDERJS).lint
doc: doc:
$(YUIDOC_CMD) . $(YUIDOC_CMD) .
clean: clean:
rm -rf $(RENDERJS_MIN) ${BUILDDIR} lib/sinon lib/jquery lib/qunit rm -rf $(RENDERJS_MIN) ${BUILDDIR} lib/sinon lib/jquery lib/qunit lib/jio
...@@ -2,3 +2,4 @@ handle relative url #parseGadgetHTML TODO ...@@ -2,3 +2,4 @@ handle relative url #parseGadgetHTML TODO
how to manage local script tag #parseGadgetHTML TODO how to manage local script tag #parseGadgetHTML TODO
check that gadget/dom context is kept in promise TODO check that gadget/dom context is kept in promise TODO
keep css file media query #declareCSS TODO keep css file media query #declareCSS TODO
handle double loading of renderjs js file TODO
<html>
<head>
<title>Catalog Gadget</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script src="../../lib/jquery/jquery.js" type="text/javascript"></script>
<script src="../../renderjs.js" type="text/javascript"></script>
<script src="catalog.js" type="text/javascript"></script>
</head>
<body>
</body>
</html>
/*global window, jQuery, rJS */
"use strict";
(function (window, $, rJS, undefined) {
var gk = rJS(window),
io_dict = {
"path" : "./io.html",
"title" : "IO",
"interface" : "http://www.renderjs.org/interface/io",
},
editor_1_dict = {
"path" : "./editor.html",
"title" : "Simple Text Editor Gadget",
"interface" : "http://www.renderjs.org/interface/editor",
},
editor_2_dict = {
"path" : "./jqteditor.html",
"title" : "JQuery Text Editor Gadget",
"interface" : "http://www.renderjs.org/interface/editor",
},
catalog_list = [
{
"path" : "./officejs.html",
"title" : "Office JS",
"interface" : "http://www.renderjs.org/interface/officejs",
},
];
catalog_list.push(io_dict);
catalog_list.push(editor_1_dict);
catalog_list.push(editor_2_dict);
gk.declareMethod('allDocs', function (filter) {
var deferred = $.Deferred();
if (filter === undefined) {
deferred.resolve(catalog_list);
} else if (filter.query ===
'interface: "http://www.renderjs.org/interface/io"') {
deferred.resolve([io_dict]);
} else if (filter.query ===
'interface: "http://www.renderjs.org/interface/editor"') {
deferred.resolve([editor_1_dict, editor_2_dict]);
} else {
deferred.reject("Unsupported filter");
}
return deferred.promise();
});
}(window, $, rJS))
<html>
<head>
<title>Simple Text Editor Gadget</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script src="../../lib/jquery/jquery.js" type="text/javascript"></script>
<script src="../../renderjs.js" type="text/javascript"></script>
<script src="editor.js" type="text/javascript"></script>
<link rel="http://www.renderjs.org/rel/interface"
href="http://www.renderjs.org/interface/editor"/>
</head>
<body>
<!--label for="textarea-a">Your basic text editor:</label-->
<textarea id="textarea-a"></textarea>
</body>
</html>
/*global window, jQuery, rJS */
"use strict";
(function (window, $, rJS) {
function escape_text(text) {
// &, ", ', <, >, /
return text.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
};
var gk = rJS(window);
gk.declareMethod('setContent', function (value) {
return rJS(this).context.find('textarea').val(escape_text(value));
})
.declareMethod('getContent', function () {
return rJS(this).context.find('textarea').val();
});
}(window, $, rJS))
<html>
<head>
<title>IO</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script src="../../lib/jquery/jquery.js" type="text/javascript"></script>
<script src="../../renderjs.js" type="text/javascript"></script>
<script src="../../lib/jio/md5.js" type="text/javascript"></script>
<script src="../../lib/jio/jio.js" type="text/javascript"></script>
<script src="../../lib/jio/complex_queries.js" type="text/javascript"></script>
<script src="../../lib/jio/localstorage.js" type="text/javascript"></script>
<script src="io.js" type="text/javascript"></script>
<link rel="http://www.renderjs.org/rel/interface"
href="http://www.renderjs.org/interface/io"/>
</head>
<body>
<a href="#" data-role="button" data-icon="check" data-inline="true">Save</a>
</body>
</html>
/*global window, jQuery, jIO, rJS */
"use strict";
(function (window, $, jIO, rJS) {
var gk = rJS(window);
gk.declareMethod('configureIO', function (key) {
rJS(this).jio = jIO.newJio({
"type": "local",
"username": "couscous",
"application_name": "renderjs"
});
rJS(this).jio_key = key;
})
.declareMethod('getIO', function () {
var deferred = $.Deferred(),
default_value = "",
gadget = rJS(this);
gadget.jio.getAttachment({
"_id": gadget.jio_key,
"_attachment": "body.txt"
}, function (err, response) {
if (err) {
if (err.status === 404) {
deferred.resolve(default_value);
} else {
deferred.reject(err);
}
} else {
deferred.resolve(response || default_value);
}
});
return deferred.promise();
})
.declareMethod('setIO', function (value) {
var deferred = $.Deferred(),
default_value = "",
gadget = rJS(this);
gadget.jio.put({"_id": gadget.jio_key},
function (err, response) {
if (err) {
deferred.reject(err);
} else {
gadget.jio.putAttachment({
"_id": gadget.jio_key,
"_attachment": "body.txt",
"_data": value,
"_mimetype": "text/plain"
}, function (err, response) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve();
}
});
}
});
return deferred.promise();
})
.declareMethod('configureDataSourceCallback', function (that, callback) {
var g = rJS(this);
g.context.find('a').unbind('click').click(function () {
callback.apply(that).done(function (value) {
g.setIO(value);
});
});
});
}(window, $, jIO, rJS))
/*!
* http://jqueryte.com
* jQuery Text Editor 1.4.0
* Copyright (C) 2012, Fatih Koca (fattih@fattih.com), AUTHOR.txt (http://jqueryte.com/about)
* This program is free software
you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation
either version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this library
if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/* editor's general field */
.jqte {
margin:30px 0;
border:#000 1px solid;
border-radius:5px; -webkit-border-radius:5px; -moz-border-radius:5px;
box-shadow:0 0 3px #999; -webkit-box-shadow:0 0 3px #999; -moz-box-shadow:0 0 3px #999;
overflow:hidden;
transition:box-shadow 0.4s, border 0.4s; -webkit-transition:-webkit-box-shadow 0.4s, border 0.4s; -moz-transition:-moz-box-shadow 0.4s, border 0.4s; -o-transition:-o-box-shadow 0.4s, border 0.4s;
}
.jqte * {
color:#333;
font-family:Arial, Helvetica, sans-serif;
font-size:14px;
}
.jqte_focused {
border-color:#00AAE7;
box-shadow:0 0 10px #00BDFF; -webkit-box-shadow:0 0 10px #00BDFF; -moz-box-shadow:0 0 10px #00BDFF;
}
/* toolbar */
.jqte_toolbar {
overflow:auto;
padding:3px 4px;
background:#EEE;
border-bottom:#BBB 1px solid;
}
.jqte_tool {
float:left;
margin:0;
padding:0;
cursor:pointer;
}
.jqte_tool, .jqte_tool_icon, .jqte_tool_label {
border:#EEE 1px solid;
border-radius:3px; -webkit-border-radius:3px; -moz-border-radius:3px
}
.jqte_hiddenField {
display:none
}
.jqte_tool_icon {
display:block;
width:22px;
height:22px;
background:url(jquery-te.png) no-repeat
}
.jqte_tool.jqte_tool_1 .jqte_tool_label {
position:relative;
display:block;
padding:3px;
width:70px;
height:16px;
overflow:hidden;
}
.jqte_tool.jqte_tool_1 .jqte_tool_text {
font:bold 13px Arial,sans-serif;
color:#222;
}
.jqte_tool.jqte_tool_1 .jqte_tool_icon {
position:absolute;
top:10px;
right:2px;
width:6px;
height:4px;
background-position:-19px -23px;
border:none;
border-radius:none; -webkit-border-radius:none; -moz-border-radius:none
}
.jqte_tool.jqte_tool_2 .jqte_tool_icon {
background-position:0 0
}
.jqte_tool.jqte_tool_3 .jqte_tool_icon {
background-position:-22px 0
}
.jqte_tool.jqte_tool_4 .jqte_tool_icon {
background-position:-44px 0
}
.jqte_tool.jqte_tool_5 .jqte_tool_icon {
background-position:-66px 0
}
.jqte_tool.jqte_tool_6 .jqte_tool_icon {
background-position:-88px 0
}
.jqte_tool.jqte_tool_7 .jqte_tool_icon {
background-position:-110px 0
}
.jqte_tool.jqte_tool_8 .jqte_tool_icon {
background-position:-132px 0
}
.jqte_tool.jqte_tool_9 .jqte_tool_icon {
background-position:-154px 0
}
.jqte_tool.jqte_tool_10 .jqte_tool_icon {
background-position:-176px 0
}
.jqte_tool.jqte_tool_11 .jqte_tool_icon {
background-position:-198px 0
}
.jqte_tool.jqte_tool_12 .jqte_tool_icon {
background-position:-220px 0
}
.jqte_tool.jqte_tool_13 .jqte_tool_icon {
background-position:-242px 0
}
.jqte_tool.jqte_tool_14 .jqte_tool_icon {
background-position:-264px 0
}
.jqte_tool.jqte_tool_15 .jqte_tool_icon {
background-position:-286px 0
}
.jqte_tool.jqte_tool_16 .jqte_tool_icon {
background-position:-308px 0
}
.jqte_tool.jqte_tool_17 .jqte_tool_icon {
background-position:-330px 0
}
.jqte_tool.jqte_tool_18 .jqte_tool_icon {
background-position:-352px 0
}
.jqte_tool.jqte_tool_19 .jqte_tool_icon {
background-position:-374px 0
}
.jqte_tool.jqte_tool_20 .jqte_tool_icon {
background-position:-396px 0
}
.jqte_tool.jqte_tool_21 .jqte_tool_icon {
background-position:-418px 0
}
.jqte_tool:hover {
background-color:#FFF;
border-color:#FFF
}
.jqte_tool:hover .jqte_tool_icon, .jqte_tool:hover .jqte_tool_label {
border:#AAA 1px solid
}
.jqte_tool:active .jqte_tool_icon, .jqte_tool:active .jqte_tool_label {
border:#777 1px solid
}
.jqte_tool.jqte_tool_1:hover .jqte_tool_icon, .jqte_tool.jqte_tool_1:active .jqte_tool_icon {
border:none
}
.jqte_tool_depressed {
background-color:#DDD;
border-color:#CCC
}
.jqte_tool_depressed .jqte_tool_icon {
border-color:#AAA
}
.jqte_tool_depressed:hover {
background-color:#EEE
}
/* link form area */
.jqte_linkform {
padding:5px 10px;
background:#DDD;
border-bottom:#BBB 1px solid;
}
.jqte_linktypeselect {
position:relative;
float:left;
width:130px;
background:#EEE;
cursor:pointer
}
.jqte_linktypeselect:Active {
background:#FFF
}
.jqte_linktypeview {
padding:3px 5px;
border:#333 1px solid;
color:#777;
border-radius:3px; -webkit-border-radius:3px; -moz-border-radius:3px;
outline:none
}
.jqte_linktypetext {
font-size:12px;
}
.jqte_linktypearrow {
position:absolute;
bottom:8px; right:6px;
width:7px;
height:6px;
margin:0 auto;
background:url(jquery-te.png) -7px -23px no-repeat;
}
.jqte_linktypes {
display:none;
position:absolute;
top:22px; left:1px;
width:125px;
background:#FFF;
border:#333 1px solid;
box-shadow:0 1px 4px #AAA; -webkit-box-shadow:0 1px 4px #AAA; -moz-box-shadow:0 1px 4px #AAA
}
.jqte_linktypes a {
display:block;
padding:4px 5px;
font-size:12px;
}
.jqte_linktypes a:hover {
background:#DDD
}
.jqte_linkinput {
float:left;
margin:0 5px;
padding:3px 5px;
width:300px;
background:#EEE;
border:#333 1px solid;
color:#777;
font-size:12px;
border-radius:3px; -webkit-border-radius:3px; -moz-border-radius:3px;
outline:none;
}
.jqte_linkinput:focus, .jqte_linkinput:hover {
background:#FFF;
border-color:#000;
}
.jqte_linkbutton {
float:left;
padding:3px 12px;
background:#AAA;
border:#333 1px solid;
color:#FFF;
font-size:12px;
font-weight:bold;
cursor:pointer;
border-radius:3px; -webkit-border-radius:3px; -moz-border-radius:3px;
box-shadow:inset 0 1px #EEE; -webkit-box-shadow:inset 0 1px #EEE; -moz-box-shadow:inset 0 1px #EEE;
}
.jqte_linkbutton:hover {
background:#A1A1A1
}
.jqte_linkbutton:active {
box-shadow:inset 0 1px #CCC; -webkit-box-shadow:inset 0 1px #CCC; -moz-box-shadow:inset 0 1px #CCC;
background:#888;
}
/* text format menu */
.jqte_formats {
display:none;
position:absolute;
width:180px;
oveflow-x:hidden;
overflow-y:auto;
background:#FFF;
border:#AAA 1px solid;
box-shadow:0 0 5px #AAA; -webkit-box-shadow:0 0 5px #AAA; -moz-box-shadow:0 0 5px #AAA;
z-index:100;
}
.jqte_format {
display:block;
padding:4px 7px;
font-size:13px
}
.jqte_format:hover {
background:#DDD
}
.jqte_format_1, .jqte_format_2, .jqte_format_3, .jqte_format_4, .jqte_format_5, .jqte_format_6 {
font-weight:bold
}
.jqte_format_1 {
font-size:22px
}
.jqte_format_2 {
font-size:20px
}
.jqte_format_3 {
font-size:18px
}
.jqte_format_4 {
font-size:16px
}
.jqte_format_5 {
font-size:14px
}
.jqte_format_6 {
font-size:12px
}
.jqte_format_7 {
font-family:"Courier New", Courier, monospace
}
/* font size menu */
.jqte_fontsizes {
display:none;
position:absolute;
width:180px;
height:198px;
oveflow-x:hidden;
overflow-y:auto;
background:#FFF;
border:#AAA 1px solid;
box-shadow:0 0 5px #AAA; -webkit-box-shadow:0 0 5px #AAA; -moz-box-shadow:0 0 5px #AAA
}
.jqte_fontsize {
display:block;
padding:3px 7px;
}
.jqte_fontsize:hover {
background:#DDD
}
/* color pallette */
.jqte_cpalette {
display:none;
position:absolute;
padding:6px;
width:144px;
background:#FFF;
border:#AAA 1px solid;
box-shadow:0 0 5px #AAA; -webkit-box-shadow:0 0 5px #AAA; -moz-box-shadow:0 0 5px #AAA
}
.jqte_color {
display:block;
float:left;
width:16px;
height:16px;
border:#FFF 1px solid;
}
.jqte_color:hover {
border-color:#000
}
.jqte_colorSeperator {
float:none;
clear:both;
height:7px;
}
/* editor area */
.jqte_editor, .jqte_source {
padding:10px;
background:#FFF;
min-height:100px;
max-height:900px;
overflow:auto;
outline:none;
word-wrap:break-word; -ms-word-wrap:break-word;
resize:vertical
}
.jqte_editor div, .jqte_editor p {
margin:0 0 7px
}
.jqte_editor a:link, .jqte_editor a:link * {
color:#0066FF !important;
text-decoration:underline;
}
.jqte_editor blockquote {
margin-top:0;
margin-bottom:7px
}
.jqte_editor img {
float:left;
margin:0 10px 5px 0
}
.jqte_editor a[jqte-setlink], .jqte_editor a[jqte-setlink] * {
background:#3297FD !important;
color:#FFF !important
}
.jqte_editor h1, .jqte_editor h2, .jqte_editor h3, .jqte_editor h4, .jqte_editor h5, .jqte_editor h6, .jqte_editor pre {
display:block;
margin:0 0 3px;
}
.jqte_editor h1, .jqte_editor h1 * {
font-size:26px
}
.jqte_editor h2, .jqte_editor h2 * {
font-size:24px
}
.jqte_editor h3, .jqte_editor h3 * {
font-size:22px
}
.jqte_editor h4, .jqte_editor h4 * {
font-size:20px
}
.jqte_editor h5, .jqte_editor h5 * {
font-size:18px
}
.jqte_editor h6, .jqte_editor h6 * {
font-size:15px
}
.jqte_editor pre, .jqte_editor pre * {
font-family:"Courier New", Courier, monospace
}
/* source area */
.jqte_source, .jqte_source textarea {
background:#FFF1E8
}
.jqte_source textarea {
margin:0 !important;
padding:0 !important;
display:block !important;
width:100% !important;
min-height:100px;
font-family:Courier, Arial, sans-serif !important;
font-weight:normal;
font-size:15px;
overflow:hidden !important;
outline:none;
resize:none;
}
.jqte_source textarea, .jqte_source textarea:focus {
css_shadow:none !important;
background:none !important;
border:none !important;
}
/* title box */
.jqte_title {
display:none;
position:absolute;
z-index:9999;
}
.jqte_titleArrow {
position:relative;
}
.jqte_titleArrowIcon {
width:7px;
height:6px;
margin:0 auto;
background:url(jquery-te.png) 0 -23px no-repeat;
}
.jqte_titleText {
padding:5px 7px;
margin-top:0;
min-width:5px;
min-height:1px;
max-width:400px;
background:#000;
border-radius:3px; -webkit-border-radius:3px; -moz-border-radius:3px;
word-wrap:break-word; -ms-word-wrap:break-word
}
.jqte_titleText, .jqte_titleText * {
color:#FFF;
font-size:11px
}
.jqte_placeholder {
position:relative;
display:none;
}
.jqte_placeholder_text {
position:absolute;
top:43px;
left:10px;
font-size:14px;
color:#CCC;
}
\ No newline at end of file
/*!
* http://jqueryte.com
* jQuery TE 1.4.0
* Copyright (C) 2012, Fatih Koca (fattih@fattih.com), AUTHOR.txt (http://jqueryte.com/about)
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
(function($){
$.fn.jqte = function(options){
// default titles of buttons
var varsTitle = [
{title:"Text Format"},
{title:"Font Size"},
{title:"Color"},
{title:"Bold",hotkey:"B"},
{title:"Italic",hotkey:"I"},
{title:"Underline",hotkey:"U"},
{title:"Ordered List",hotkey:"."},
{title:"Unordered List",hotkey:","},
{title:"Subscript",hotkey:"down arrow"},
{title:"Superscript",hotkey:"up arrow"},
{title:"Outdent",hotkey:"left arrow"},
{title:"Indent",hotkey:"right arrow"},
{title:"Justify Left"},
{title:"Justify Center"},
{title:"Justify Right"},
{title:"Strike Through",hotkey:"K"},
{title:"Add Link",hotkey:"L"},
{title:"Remove Link"},
{title:"Cleaner Style",hotkey:"Delete"},
{title:"Horizontal Rule",hotkey:"H"},
{title:"Source"}
];
// default text formats
var formats = [["p","Normal"],["h1","Header 1"],["h2","Header 2"],["h3","Header 3"],["h4","Header 4"],["h5","Header 5"],["h6","Header 6"],["pre","Preformatted"]];
// default font sizes
var fsizes = ["10","12","16","18","20","24","28"];
// default rgb values of colors
var colors = [
"0,0,0","68,68,68","102,102,102","153,153,153","204,204,204","238,238,238","243,243,243","255,255,255",
null,
"255,0,0","255,153,0","255,255,0","0,255,0","0,255,255","0,0,255","153,0,255","255,0,255",
null,
"244,204,204","252,229,205","255,242,204","217,234,211","208,224,227","207,226,243","217,210,233","234,209,220",
"234,153,153","249,203,156","255,229,153","182,215,168","162,196,201","159,197,232","180,167,214","213,166,189",
"224,102,102","246,178,107","255,217,102","147,196,125","118,165,175","111,168,220","142,124,195","194,123,160",
"204,0,0","230,145,56","241,194,50","106,168,79","69,129,142","61,133,198","103,78,167","166,77,121",
"153,0,0","180,95,6","191,144,0","56,118,29","19,79,92","11,83,148","53,28,117","116,27,71",
"102,0,0","120,63,4","127,96,0","39,78,19","12,52,61","7,55,99","32,18,77","76,17,48"
];
// default link-type names
var linktypes = ["Web Address","E-mail Address","Picture URL"];
var vars = $.extend({
// options
'status' : true,
'css' : "jqte",
'title' : true,
'titletext' : varsTitle,
'button' : "OK",
'format' : true,
'formats' : formats,
'fsize' : true,
'fsizes' : fsizes,
'funit' : "px",
'color' : true,
'linktypes' : linktypes,
'b' : true,
'i' : true,
'u' : true,
'ol' : true,
'ul' : true,
'sub' : true,
'sup' : true,
'outdent' : true,
'indent' : true,
'left' : true,
'center' : true,
'right' : true,
'strike' : true,
'link' : true,
'unlink' : true,
'remove' : true,
'rule' : true,
'source' : true,
'placeholder' : false,
'br' : true,
'p' : true,
// events
'change' : "",
'focus' : "",
'blur' : ""
}, options);
// methods
$.fn.jqteVal = function(value){
$(this).closest("."+vars.css).find("."+vars.css+"_editor").html(value);
}
// browser information is received
var thisBrowser = navigator.userAgent.toLowerCase();
// if browser is ie and it version is 7 or even older, close title property
if(/msie [1-7]./.test(thisBrowser))
vars.title = false;
var buttons = [];
// insertion function for parameters to toolbar
function addParams(name,command,key,tag,emphasis)
{
var thisCssNo = buttons.length+1;
return buttons.push({name:name, cls:thisCssNo, command:command, key:key, tag:tag, emphasis:emphasis});
};
// add parameters for toolbar buttons
addParams('format','formats','','',false); // text format button --> no hotkey
addParams('fsize','fSize','','',false); // font size button --> no hotkey
addParams('color','colors','','',false); // text color button --> no hotkey
addParams('b','Bold','B',["b","strong"],true); // bold --> ctrl + b
addParams('i','Italic','I',["i","em"],true); // italic --> ctrl + i
addParams('u','Underline','U',["u"],true); // underline --> ctrl + u
addParams('ol','insertorderedlist','¾',["ol"],true); // ordered list --> ctrl + .(dot)
addParams('ul','insertunorderedlist','¼',["ul"],true); // unordered list --> ctrl + ,(comma)
addParams('sub','subscript','(',["sub"],true); // sub script --> ctrl + down arrow
addParams('sup','superscript','&',["sup"],true); // super script --> ctrl + up arrow
addParams('outdent','outdent','%',["blockquote"],false); // outdent --> ctrl + left arrow
addParams('indent','indent','\'',["blockquote"],true); // indent --> ctrl + right arrow
addParams('left','justifyLeft','','',false); // justify Left --> no hotkey
addParams('center','justifyCenter','','',false); // justify center --> no hotkey
addParams('right','justifyRight','','',false); // justify right --> no hotkey
addParams('strike','strikeThrough','K',["strike"],true); // strike through --> ctrl + K
addParams('link','linkcreator','L',["a"],true); // insertion link --> ctrl + L
addParams('unlink','unlink','',["a"],false); // remove link --> ctrl + N
addParams('remove','removeformat','.','',false); // remove all styles --> ctrl + delete
addParams('rule','inserthorizontalrule','H',["hr"],false); // insertion horizontal rule --> ctrl + H
addParams('source','displaysource','','',false); // feature of displaying source
return this.each(function(){
if(!$(this).data("jqte") || $(this).data("jqte")==null || $(this).data("jqte")=="undefined")
$(this).data("jqte",true);
else
$(this).data("jqte",false);
// is the status false of the editor
if(!vars.status || !$(this).data("jqte"))
{
// if wanting the false status later
if($(this).closest("."+vars.css).length>0)
{
var editorValue = $(this).closest("."+vars.css).find("."+vars.css+"_editor").html();
// add all attributes of element
var thisElementAttrs = "";
$($(this)[0].attributes).each(function()
{
if(this.nodeName!="style")
thisElementAttrs = thisElementAttrs+" "+this.nodeName+'="'+this.nodeValue+'"';
});
var thisElementTag = $(this).is("[data-origin]") && $(this).attr("data-origin")!="" ? $(this).attr("data-origin") : "textarea";
// the contents of this element
var createValue = '>'+editorValue;
// if this element is input or option
if(thisElementTag=="input" || thisElementTag=="option")
{
// encode special html characters
editorValue = editorValue.replace(/"/g,'&#34;').replace(/'/g,'&#39;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
// the value of this element
createValue = 'value="'+editorValue+'">';
}
var thisClone = $(this).clone();
$(this).data("jqte",false).closest("."+vars.css).before(thisClone).remove();
thisClone.replaceWith('<'+ thisElementTag + thisElementAttrs + createValue + '</'+thisElementTag+'>');
}
return;
}
// element will converted to the jqte editor
var thisElement = $(this);
// tag name of the element
var thisElementTag = $(this).prop('tagName').toLowerCase();
// tag name of origin
$(this).attr("data-origin",thisElementTag);
// contents of the element
var thisElementVal = $(this).is("[value]") || thisElementTag == "textarea" ? $(this).val() : $(this).html();
// decode special html characters
thisElementVal = thisElementVal.replace(/&#34;/g,'"').replace(/&#39;/g,"'").replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&');
// start jqte editor to after the element
$(this).after('<div class="'+vars.css+'"></div>');
// jqte
var jQTE = $(this).next('.'+vars.css);
// insert toolbar in jqte editor
jQTE.html('<div class="'+vars.css+"_toolbar"+'" role="toolbar" unselectable></div><div class="'+vars.css+'_linkform" style="display:none" role="dialog"></div><div class="'+vars.css+"_editor"+'"></div>');
var toolbar = jQTE.find('.'+vars.css+"_toolbar"); // the toolbar variable
var linkform = jQTE.find('.'+vars.css+"_linkform"); // the link-form-area in the toolbar variable
var editor = jQTE.find('.'+vars.css+"_editor"); // the text-field of jqte editor
var emphasize = vars.css+"_tool_depressed"; // highlight style of the toolbar buttons
// add to some tools in link form area
linkform.append('<div class="'+vars.css+'_linktypeselect" unselectable></div><input class="'+vars.css+'_linkinput" type="text/css" value=""><div class="'+vars.css+'_linkbutton" unselectable>'+vars.button+'</div> <div style="height:1px;float:none;clear:both"></div>');
var linktypeselect = linkform.find("."+vars.css+"_linktypeselect"); // the tool of link-type-selector
var linkinput = linkform.find("."+vars.css+"_linkinput"); // the input of insertion link
var linkbutton = linkform.find("."+vars.css+"_linkbutton"); // the button of insertion link
// add to the link-type-selector sub tool parts
linktypeselect.append('<div class="'+vars.css+'_linktypeview" unselectable></div><div class="'+vars.css+'_linktypes" role="menu" unselectable></div>');
var linktypes = linktypeselect.find("."+vars.css+"_linktypes"); // the select box of link types
var linktypeview = linktypeselect.find("."+vars.css+"_linktypeview"); // the link type preview
var setdatalink = vars.css+"-setlink"; // the selected text add to mark as "link will be added"
// create to the source-area
editor.after('<div class="'+vars.css+'_source '+vars.css+'_hiddenField"></div>');
var sourceField = jQTE.find("."+vars.css+"_source"); // the source-area variable
// move the element to the source-area
thisElement.appendTo(sourceField);
// if the element isn't a textarea, convert this to textarea
if(thisElementTag!="textarea")
{
// add all attributes of element to new textarea (type and value except)
var thisElementAttrs = "";
$(thisElement[0].attributes).each(function(){
if(this.nodeName!="type" && this.nodeName!="value")
thisElementAttrs = thisElementAttrs+" "+this.nodeName+'="'+this.nodeValue+'"';
});
// convert the element to textarea
thisElement.replaceWith('<textarea '+thisElementAttrs+'>'+thisElementVal+'</textarea>');
// update to variable of thisElement
thisElement = sourceField.find("textarea");
}
// add feature editable to the text-field ve copy from the element's value to text-field
editor.attr("contenteditable","true").html(thisElementVal);
// insertion the toolbar button
for(var n = 0; n < buttons.length; n++)
{
// if setting of this button is activated (is it true?)
if(vars[buttons[n].name])
{
// if it have a title, add to this button
var buttonHotkey = buttons[n].key.length>0 ? vars.titletext[n].hotkey!=null && vars.titletext[n].hotkey!="undefined" && vars.titletext[n].hotkey!="" ? ' (Ctrl+'+vars.titletext[n].hotkey+')' : '' : '';
var buttonTitle = vars.titletext[n].title!=null && vars.titletext[n].title!="undefined" && vars.titletext[n].title!="" ? vars.titletext[n].title+buttonHotkey : '';
// add this button to the toolbar
toolbar.append('<div class="'+vars.css+'_tool '+vars.css+'_tool_'+buttons[n].cls+'" role="button" data-tool="'+n+'" unselectable><a class="'+vars.css+'_tool_icon" unselectable></a></div>');
// add the parameters to this button
toolbar.find('.'+vars.css+'_tool[data-tool='+n+']').data({tag : buttons[n].tag, command : buttons[n].command, emphasis : buttons[n].emphasis, title : buttonTitle});
// format-selector field
if(buttons[n].name=="format" && $.isArray(vars.formats))
{
// selected text format
var toolLabel = vars.formats[0][1].length>0 && vars.formats[0][1]!="undefined" ? vars.formats[0][1] : "";
toolbar.find("."+vars.css+'_tool_'+buttons[n].cls).find("."+vars.css+"_tool_icon").replaceWith('<a class="'+vars.css+'_tool_label" unselectable><span class="'+vars.css+'_tool_text" unselectable>'+toolLabel+'</span><span class="'+vars.css+'_tool_icon" unselectable></span></a>');
toolbar.find("."+vars.css+'_tool_'+buttons[n].cls)
.append('<div class="'+vars.css+'_formats" unselectable></div>');
// add font-sizes to font-size-selector
for(var f = 0; f < vars.formats.length; f++)
{
toolbar.find("."+vars.css+"_formats").append('<a '+vars.css+'-formatval="'+ vars.formats[f][0] +'" class="'+vars.css+'_format'+' '+vars.css+'_format_'+f+'" role="menuitem" unselectable>'+ vars.formats[f][1] +'</a>');
}
toolbar.find("."+vars.css+"_formats").data("status",false);
}
// font-size-selector field
else if(buttons[n].name=="fsize" && $.isArray(vars.fsizes))
{
toolbar.find("."+vars.css+'_tool_'+buttons[n].cls)
.append('<div class="'+vars.css+'_fontsizes" unselectable></div>');
// add font-sizes to font-size-selector
for(var f = 0; f < vars.fsizes.length; f++)
{
toolbar.find("."+vars.css+"_fontsizes").append('<a '+vars.css+'-styleval="'+ vars.fsizes[f] +'" class="'+vars.css+'_fontsize'+'" style="font-size:'+ vars.fsizes[f] + vars.funit+'" role="menuitem" unselectable>Abcdefgh...</a>');
}
}
// color-selector field
else if(buttons[n].name=="color" && $.isArray(colors))
{
toolbar.find("."+vars.css+'_tool_'+buttons[n].cls)
.append('<div class="'+vars.css+'_cpalette" unselectable></div>');
// create color palette to color-selector field
for(var c = 0; c < colors.length; c++)
{
if(colors[c]!=null)
toolbar.find("."+vars.css+"_cpalette").append('<a '+vars.css+'-styleval="'+ colors[c] +'" class="'+vars.css+'_color'+'" style="background-color: rgb('+ colors[c] +')" role="gridcell" unselectable></a>');
else
toolbar.find("."+vars.css+"_cpalette").append('<div class="'+vars.css+"_colorSeperator"+'"></div>');
}
}
}
}
// the default value of the link-type
linktypes.data("linktype","0");
// add link types to link-type-selector
for(var n = 0; n < 3; n++)
{
linktypes.append('<a '+vars.css+'-linktype="'+n+'" unselectable>'+vars.linktypes[n]+'</a>');
linktypeview.html('<div class="'+vars.css+'_linktypearrow" unselectable></div><div class="'+vars.css+'_linktypetext">'+linktypes.find('a:eq('+linktypes.data("linktype")+')').text()+'</div>');
}
// add the prefix of css according to browser
var prefixCss = "";
if(/msie/.test(thisBrowser)) // ie
prefixCss = '-ms-';
else if(/chrome/.test(thisBrowser) || /safari/.test(thisBrowser) || /yandex/.test(thisBrowser)) // webkit group (safari, chrome, yandex)
prefixCss = '-webkit-';
else if(/mozilla/.test(thisBrowser)) // firefox
prefixCss = '-moz-';
else if(/opera/.test(thisBrowser)) // opera
prefixCss = '-o-';
else if(/konqueror/.test(thisBrowser)) // konqueror
prefixCss = '-khtml-';
else
prefixCss = '';
// the feature of placeholder
if(vars.placeholder && vars.placeholder!="")
{
jQTE.prepend('<div class="'+vars.css+'_placeholder" unselectable><div class="'+vars.css+'_placeholder_text">'+vars.placeholder+'</div></div>');
var placeHolder = jQTE.find("."+vars.css+"_placeholder");
placeHolder.click(function(){
editor.focus();
});
}
// make unselectable to unselectable attribute ones
jQTE.find("[unselectable]")
.css(prefixCss+"user-select","none")
.addClass("unselectable")
.attr("unselectable","on")
.on("selectstart mousedown",false);
// each button of the toolbar
var toolbutton = toolbar.find("."+vars.css+"_tool");
// format menu
var formatbar = toolbar.find("."+vars.css+"_formats");
// font-size filed
var fsizebar = toolbar.find("."+vars.css+"_fontsizes");
// color palette
var cpalette = toolbar.find("."+vars.css+"_cpalette");
// get the selected text as plain format
function selectionGet()
{
// for webkit, mozilla, opera
if (window.getSelection)
return window.getSelection();
// for ie
else if (document.selection && document.selection.createRange && document.selection.type != "None")
return document.selection.createRange();
}
// the function of changing to the selected text with "execCommand" method
function selectionSet(addCommand,thirdParam)
{
var range,
sel = selectionGet();
// for webkit, mozilla, opera
if (window.getSelection)
{
if (sel.anchorNode && sel.getRangeAt)
range = sel.getRangeAt(0);
if(range)
{
sel.removeAllRanges();
sel.addRange(range);
}
if(!thisBrowser.match(/msie/))
document.execCommand('StyleWithCSS', false, false);
document.execCommand(addCommand, false, thirdParam);
}
// for ie
else if (document.selection && document.selection.createRange && document.selection.type != "None")
{
range = document.selection.createRange();
range.execCommand(addCommand, false, thirdParam);
}
// change styles to around tags
affectStyleAround(false,false);
}
// the function of changing to the selected text with tags and tags's attributes
function replaceSelection(tTag,tAttr,tVal) {
// first, prevent to conflict of different jqte editors
if(editor.not(":focus"))
editor.focus();
// for webkit, mozilla, opera
if (window.getSelection)
{
var selObj = selectionGet(), selRange, newElement, documentFragment;
if (selObj.anchorNode && selObj.getRangeAt)
{
selRange = selObj.getRangeAt(0);
// create to new element
newElement = document.createElement(tTag);
// add the attribute to the new element
$(newElement).attr(tAttr,tVal);
// extract to the selected text
documentFragment = selRange.extractContents();
// add the contents to the new element
newElement.appendChild(documentFragment);
selRange.insertNode(newElement);
selObj.removeAllRanges();
// if the attribute is "style", change styles to around tags
if(tAttr=="style")
affectStyleAround($(newElement),tVal);
// for other attributes
else
affectStyleAround($(newElement),false);
}
}
// for ie
else if (document.selection && document.selection.createRange && document.selection.type != "None")
{
var range = document.selection.createRange();
var selectedText = range.htmlText;
var newText = '<'+tTag+' '+tAttr+'="'+tVal+'">'+selectedText+'</'+tTag+'>';
document.selection.createRange().pasteHTML(newText);
}
}
// the function of getting to the parent tag
var getSelectedNode = function() {
var node,selection;
if(window.getSelection) {
selection = getSelection();
node = selection.anchorNode;
}
if(!node && document.selection && document.selection.createRange && document.selection.type != "None")
{
selection = document.selection;
var range = selection.getRangeAt ? selection.getRangeAt(0) : selection.createRange();
node = range.commonAncestorContainer ? range.commonAncestorContainer :
range.parentElement ? range.parentElement() : range.item(0);
}
if(node) {
return (node.nodeName == "#text" ? $(node.parentNode) : $(node));
}
else
return false;
};
// the function of replacement styles to the around tags (parent and child)
function affectStyleAround(element,style)
{
var selectedTag = getSelectedNode(); // the selected node
selectedTag = selectedTag ? selectedTag : element;
// (for replacement with execCommand) affect to child tags with parent tag's styles
if(selectedTag && style==false)
{
// apply to the selected node with parent tag's styles
if(selectedTag.parent().is("[style]"))
selectedTag.attr("style",selectedTag.parent().attr("style"));
// apply to child tags with parent tag's styles
if(selectedTag.is("[style]"))
selectedTag.find("*").attr("style",selectedTag.attr("style"));
}
// (for replacement with html changing method)
else if(element && style && element.is("[style]"))
{
var styleKey = style.split(";"); // split the styles
styleKey = styleKey[0].split(":") // get the key of first style feature
// apply to child tags with parent tag's styles
if(element.is("[style*="+styleKey[0]+"]"))
element.find("*").css(styleKey[0],styleKey[1]);
// select to the selected node again
selectText(element);
}
}
// the function of making selected to a element
function selectText(element)
{
if(element)
{
var element = element[0];
if (document.body.createTextRange)
{
var range = document.body.createTextRange();
range.moveToElementText(element);
range.select();
}
else if (window.getSelection)
{
var selection = window.getSelection();
var range = document.createRange();
if(element != "undefined" && element != null)
{
range.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(range);
if($(element).is(":empty"))
{
$(element).append("&nbsp;");
selectText($(element));
}
}
}
}
}
// the function of converting text to link
function selected2link()
{
if(!toolbar.data("sourceOpened"))
{
var selectedTag = getSelectedNode(); // the selected node
var thisHrefLink = "http://"; // default the input value of the link-form-field
// display the link-form-field
linkAreaSwitch(true);
if(selectedTag)
{
var thisTagName = selectedTag.prop('tagName').toLowerCase();
// if tag name of the selected node is "a" and the selected node have "href" attribute
if(thisTagName == "a" && selectedTag.is('[href]'))
{
thisHrefLink = selectedTag.attr('href');
selectedTag.attr(setdatalink,"");
}
// if it don't have "a" tag name
else
replaceSelection("a",setdatalink,"");
}
else
linkinput.val(thisHrefLink).focus();
// the method of displaying-hiding to link-types
linktypeselect.click(function(e)
{
if($(e.target).hasClass(vars.css+"_linktypetext") || $(e.target).hasClass(vars.css+"_linktypearrow"))
linktypeSwitch(true);
});
// the method of selecting to link-types
linktypes.find("a").click(function()
{
var thisLinkType = $(this).attr(vars.css+"-linktype");
linktypes.data("linktype",thisLinkType)
linktypeview.find("."+vars.css+"_linktypetext").html(linktypes.find('a:eq('+linktypes.data("linktype")+')').text());
linkInputSet(thisHrefLink);
linktypeSwitch();
});
linkInputSet(thisHrefLink);
// the method of link-input
linkinput
// auto focus
.focus()
// update to value
.val(thisHrefLink)
// the event of key to enter in link-input
.bind("keypress keyup",function(e)
{
if(e.keyCode==13)
{
linkRecord(jQTE.find("["+setdatalink+"]"));
return false;
}
});
// the event of click link-button
linkbutton.click(function()
{
linkRecord(jQTE.find("["+setdatalink+"]"));
});
}
else
// hide the link-form-field
linkAreaSwitch(false);
}
function linkRecord(thisSelection)
{
// focus to link-input
linkinput.focus();
// select to the selected node
selectText(thisSelection);
// remove pre-link attribute (mark as "link will be added") of the selected node
thisSelection.removeAttr(setdatalink);
// if not selected to link-type of picture
if(linktypes.data("linktype")!="2")
selectionSet("createlink",linkinput.val()); // insert link url of link-input to the selected node
// if selected to link-type of picture
else
{
selectionSet("insertImage",linkinput.val()); // insert image url of link-input to the selected node
// the method of all pictures in the editor
editor.find("img").each(function(){
var emptyPrevLinks = $(this).prev("a");
var emptyNextLinks = $(this).next("a");
// if "a" tags of the front and rear of the picture is empty, remove
if(emptyPrevLinks.length>0 && emptyPrevLinks.html()=="")
emptyPrevLinks.remove();
else if(emptyNextLinks.length>0 && emptyNextLinks.html()=="")
emptyNextLinks.remove();
});
}
// hide the link-form-field
linkAreaSwitch();
// export contents of the text to the sources
editor.trigger("change");
}
// the function of switching link-form-field
function linkAreaSwitch(status)
{
// remove all pre-link attribute (mark as "link will be added")
clearSetElement("["+setdatalink+"]:not([href])");
jQTE.find("["+setdatalink+"][href]").removeAttr(setdatalink);
if(status)
{
toolbar.data("linkOpened",true);
linkform.show();
}
else
{
toolbar.data("linkOpened",false);
linkform.hide();
}
linktypeSwitch();
}
// the function of switching link-type-selector
function linktypeSwitch(status)
{
if(status)
linktypes.show();
else
linktypes.hide();
}
// the function of updating the link-input according to the link-type
function linkInputSet(thisHrefLink)
{
var currentType = linktypes.data("linktype");
// if selected type of e-mail
if(currentType=="1" && (linkinput.val()=="http://" || linkinput.is("[value^=http://]") || !linkinput.is("[value^=mailto]")))
linkinput.val("mailto:");
else if(currentType!="1" && !linkinput.is("[value^=http://]"))
linkinput.val("http://");
else
linkinput.val(thisHrefLink);
}
// the function of adding style to selected text
function selected2style(styleCommand)
{
if(!toolbar.data("sourceOpened"))
{
// if selected to changing the font-size value
if(styleCommand=="fSize")
styleField = fsizebar;
// if selected to changing the text-color value
else if(styleCommand=="colors")
styleField = cpalette;
// display the style-field
styleFieldSwitch(styleField,true);
// the event of click to style button
styleField.find("a").unbind("click").click(function()
{
var styleValue = $(this).attr(vars.css + "-styleval"); // the property of style value to be added
// if selected to changing the font-size value
if(styleCommand=="fSize")
{
styleType = "font-size";
styleValue = styleValue + vars.funit; // combine the value with size unit
}
// if selected to changing the text-color value
else if(styleCommand=="colors")
{
styleType = "color";
styleValue = "rgb("+styleValue + ")"; // combine color value with rgb
}
var prevStyles = refuseStyle(styleType); // affect styles to child tags (and extract to the new style attributes)
// change to selected text
replaceSelection("span","style",styleType+":"+styleValue+";"+prevStyles);
// hide all style-fields
styleFieldSwitch("",false);
// remove title bubbles
$('.'+vars.css+'_title').remove();
// export contents of the text to the sources
editor.trigger("change");
});
}
else
// hide the style-field
styleFieldSwitch(styleField,false);
// hide the link-form-field
linkAreaSwitch(false);
}
// the function of switching the style-field
function styleFieldSwitch(styleField,status)
{
var mainData="", // the style data of the actual wanted
allData = [{"d":"fsizeOpened","f":fsizebar},{"d":"cpallOpened","f":cpalette}]; // all style datas
// if the style data of the actual wanted isn't empty
if(styleField!="")
{
// return to all datas and find the main data
for(var si=0; si < allData.length; si++)
{
if(styleField==allData[si]["f"])
mainData = allData[si];
}
}
// display the style-field
if(status)
{
toolbar.data(mainData["d"],true); // stil seçme alanının açıldığını belirten parametre yaz
mainData["f"].slideDown(100); // stil seçme alanını aç
// return to all datas and close the fields of external datas
for(var si=0; si < allData.length; si++)
{
if(mainData["d"]!=allData[si]["d"])
{
toolbar.data(allData[si]["d"],false);
allData[si]["f"].slideUp(100);
}
}
}
// hide all style-fields
else
{
// return to all datas and close all style fields
for(var si=0; si < allData.length; si++)
{
toolbar.data(allData[si]["d"],false);
allData[si]["f"].slideUp(100);
}
}
}
// the function of removing all pre-link attribute (mark as "link will be added")
function clearSetElement(elem)
{
jQTE.find(elem).each(function(){
$(this).before($(this).html()).remove();
});
}
// the function of refusing some styles
function refuseStyle(refStyle)
{
var selectedTag = getSelectedNode(); // the selected node
// if the selected node have attribute of "style" and it have unwanted style
if(selectedTag && selectedTag.is("[style]") && selectedTag.css(refStyle)!="")
{
var refValue = selectedTag.css(refStyle); // first get key of unwanted style
selectedTag.css(refStyle,""); // clear unwanted style
var cleanStyle = selectedTag.attr("style"); // cleaned style
selectedTag.css(refStyle,refValue); // add unwanted style to the selected node again
return cleanStyle; // print cleaned style
}
else
return "";
}
// the function of adding style to selected text
function selected2format()
{
formatFieldSwitch(true);
formatbar.find("a").click(function()
{
$("*",this).click(function(e)
{
e.preventDefault();
return false;
});
formatLabelView($(this).text());
var formatValue = $(this).attr(vars.css + "-formatval"); // the type of format value
// convert to selected format
selectionSet("formatBlock",'<'+formatValue+'>');
formatFieldSwitch(false);
});
}
// the function of switching the style-field
function formatFieldSwitch(status)
{
var thisStatus = status ? true : false;
thisStatus = status && formatbar.data("status") ? true : false;
if(thisStatus || !status)
formatbar.data("status",false).slideUp(200);
else
formatbar.data("status",true).slideDown(200);
}
// change format label
function formatLabelView(str)
{
var formatLabel = formatbar.closest("."+vars.css+"_tool").find("."+vars.css+"_tool_label").find("."+vars.css+"_tool_text");
if(str.length > 10)
str = str.substr(0,7) + "...";
// change format label of button
formatLabel.html(str);
}
// the function of insertion a specific form to texts
function extractToText(strings)
{
var $htmlContent, $htmlPattern, $htmlReplace;
// first remove to unnecessary gaps
$htmlContent = strings.replace(/\n/gim,'').replace(/\r/gim,'').replace(/\t/gim,'').replace(/&nbsp;/gim,' ');
$htmlPattern = [
/\<span(|\s+.*?)><span(|\s+.*?)>(.*?)<\/span><\/span>/gim, // trim nested spans
/<(\w*[^p])\s*[^\/>]*>\s*<\/\1>/gim, // remove empty or white-spaces tags (ignore paragraphs (<p>) and breaks (<br>))
/\<div(|\s+.*?)>(.*?)\<\/div>/gim, // convert div to p
/\<strong(|\s+.*?)>(.*?)\<\/strong>/gim, // convert strong to b
/\<em(|\s+.*?)>(.*?)\<\/em>/gim // convert em to i
];
$htmlReplace = [
'<span$2>$3</span>',
'',
'<p$1>$2</p>',
'<b$1>$2</b>',
'<i$1>$2</i>'
];
// repeat the cleaning process 5 times
for(c=0; c<5; c++)
{
// create loop as the number of pattern
for(var i = 0; i < $htmlPattern.length; i++)
{
$htmlContent = $htmlContent.replace($htmlPattern[i], $htmlReplace[i]);
}
}
// if paragraph is false (<p>), convert <p> to <br>
if(!vars.p)
$htmlContent = $htmlContent.replace(/\<p(|\s+.*?)>(.*?)\<\/p>/ig, '<br/>$2');
// if break is false (<br>), convert <br> to <p>
if(!vars.br)
{
$htmlPattern = [
/\<br>(.*?)/ig,
/\<br\/>(.*?)/ig
];
$htmlReplace = [
'<p>$1</p>',
'<p>$1</p>'
];
// create loop as the number of pattern (for breaks)
for (var i = 0; i < $htmlPattern.length; i++) {
$htmlContent = $htmlContent.replace($htmlPattern[i], $htmlReplace[i]);
}
}
// if paragraph and break is false (<p> && <br>), convert <p> to <div>
if(!vars.p && !vars.br)
$htmlContent = $htmlContent.replace(/\<p>(.*?)\<\/p>/ig, '<div>$1</div>');
return $htmlContent;
}
// the function of exporting contents of the text field to the source field (to be the standard in all browsers)
function postToSource()
{
// clear unnecessary tags when editor view empty
var sourceStrings = editor.text()=="" && editor.html().length<12 ? "" : editor.html();
thisElement.val(extractToText(sourceStrings));
}
// the function of exporting contents of the source field to the text field (to be the standard in all browsers)
function postToEditor()
{
editor.html(extractToText(thisElement.val()));
}
// the function of getting parent (or super parent) tag name of the selected node
function detectElement(tags){
var resultdetect=false, $node = getSelectedNode(), parentsTag;
if($node)
{
$.each(tags, function(i, val){
parentsTag = $node.prop('tagName').toLowerCase();
if (parentsTag == val)
resultdetect = true;
else
{
$node.parents().each(function(){
parentsTag = $(this).prop('tagName').toLowerCase();
if (parentsTag == val)
resultdetect = true;
});
}
});
return resultdetect;
}
else
return false;
};
// the function of highlighting the toolbar buttons according to the cursor position in jqte editor
function buttonEmphasize(e)
{
for(var n = 0; n < buttons.length; n++)
{
if(vars[buttons[n].name] && buttons[n].emphasis && buttons[n].tag!='')
detectElement(buttons[n].tag) ? toolbar.find('.'+vars.css+'_tool_'+buttons[n].cls).addClass(emphasize) : $('.'+vars.css+'_tool_'+buttons[n].cls).removeClass(emphasize);
}
// showing text format
if(vars.format && $.isArray(vars.formats))
{
var isFoundFormat = false;
for(var f = 0; f < vars.formats.length; f++)
{
var thisFormat = [];
thisFormat[0] = vars.formats[f][0];
if(vars.formats[f][0].length>0 && detectElement(thisFormat))
{
formatLabelView(vars.formats[f][1]);
isFoundFormat = true;
break;
}
}
if(!isFoundFormat)
formatLabelView(vars.formats[0][1]);
}
// hide all style-fields
styleFieldSwitch("",false);
formatFieldSwitch(false);
}
// the event of click to the toolbar buttons
toolbutton
.unbind("click")
.click(function(e){
// if source button is clicked
if($(this).data('command')=='displaysource' && !toolbar.data("sourceOpened"))
{
// hide all the toolbar buttons (except the source button)
toolbar.find("."+vars.css+"_tool").addClass(vars.css+"_hiddenField");
$(this).removeClass(vars.css+"_hiddenField");
// update to data of source displaying
toolbar.data("sourceOpened",true);
// equalize height of the text field with height of the source field
thisElement.css("height",editor.outerHeight());
sourceField.removeClass(vars.css+"_hiddenField");
editor.addClass(vars.css+"_hiddenField");
thisElement.focus();
// hide the link-form-field
linkAreaSwitch(false);
// hide all style-fields
styleFieldSwitch("",false);
// hide format field
formatFieldSwitch();
// hide placeholder
if(vars.placeholder && vars.placeholder!="")
placeHolder.hide();
}
// if other buttons is clicked
else
{
// if source field is closed
if(!toolbar.data("sourceOpened"))
{
// if insert-link-button is clicked
if($(this).data('command')=='linkcreator')
{
if(!toolbar.data("linkOpened"))
selected2link();
else
{
// hide the link-form-field
linkAreaSwitch(false);
// hide format field
formatFieldSwitch(false);
}
}
// if the format button is clicked
else if($(this).data('command')=='formats')
{
if($(this).data('command')=='formats' && !$(e.target).hasClass(vars.css+"_format"))
selected2format();
// hide all style-fields
styleFieldSwitch("",false);
if(editor.not(":focus"))
editor.focus();
}
// if the style buttons are clicked
else if($(this).data('command')=='fSize' || $(this).data('command')=='colors')
{
if(
($(this).data('command')=='fSize' && !$(e.target).hasClass(vars.css+"_fontsize")) || // the font-size button
($(this).data('command')=='colors' && !$(e.target).hasClass(vars.css+"_color")) // the color button
)
selected2style($(this).data('command'));
// hide format field
formatFieldSwitch(false);
if(editor.not(":focus"))
editor.focus();
}
// if other buttons is clicked
else
{
// first, prevent to conflict of different jqte editors
if(editor.not(":focus"))
editor.focus();
// apply command of clicked button to the selected text
selectionSet($(this).data('command'),null);
// hide all menu-fields
styleFieldSwitch("",false);
formatFieldSwitch(false);
linktypeSwitch();
// to highlight the toolbar buttons according to the cursor position in jqte editor
$(this).data('emphasis')==true && !$(this).hasClass(emphasize) ? $(this).addClass(emphasize) : $(this).removeClass(emphasize);
sourceField.addClass(vars.css+"_hiddenField");
editor.removeClass(vars.css+"_hiddenField");
}
}
// hide the source field and display the text field
else
{
// update to data of source hiding
toolbar.data("sourceOpened",false);
// display all the toolbar buttons
toolbar.find("."+vars.css+"_tool").removeClass(vars.css+"_hiddenField");
sourceField.addClass(vars.css+"_hiddenField");
editor.removeClass(vars.css+"_hiddenField");
}
if(vars.placeholder && vars.placeholder!="")
editor.html()!="" ? placeHolder.hide() : placeHolder.show();
}
// export contents of the text to the sources
editor.trigger("change");
})
// the event of showing to the title bubble when mouse over of the toolbar buttons
.hover(function(e){
if(vars.title && $(this).data("title")!="" && ( $(e.target).hasClass(vars.css+"_tool") || $(e.target).hasClass(vars.css+"_tool_icon") ))
{
$('.'+vars.css+'_title').remove();
// create the title bubble
jQTE.append('<div class="'+vars.css+'_title"><div class="'+vars.css+'_titleArrow"><div class="'+vars.css+'_titleArrowIcon"></div></div><div class="'+vars.css+'_titleText">'+$(this).data("title")+'</div></div>');
var thisTitle = $('.'+vars.css+'_title:first');
var thisArrow = thisTitle.find('.'+vars.css+'_titleArrowIcon');
var thisPosition = $(this).position();
var thisAlignX = thisPosition.left + $(this).outerWidth() - (thisTitle.outerWidth()/2) - ($(this).outerWidth()/2);
var thisAlignY = (thisPosition.top + $(this).outerHeight() + 5);
// show the title bubble and set to its position
thisTitle.delay(400).css({'top':thisAlignY, 'left':thisAlignX}).fadeIn(200);
}
},function(){
$('.'+vars.css+'_title').remove();
});
// prevent multiple calling postToSource()
var editorChangeTimer = null;
// the methods of the text fields
editor
// trigger change method of the text field when the text field modified
.bind("keypress keyup keydown drop cut copy paste DOMCharacterDataModified DOMSubtreeModified",function()
{
// export contents of the text to the sources
if(!toolbar.data("sourceOpened"))
$(this).trigger("change");
// hide the link-type-field
linktypeSwitch();
// if the change method is added run the change method
if($.isFunction(vars.change))
vars.change();
// the feature of placeholder
if(vars.placeholder && vars.placeholder!="")
$(this).text()!="" ? placeHolder.hide() : placeHolder.show();
})
.bind("change",function()
{
if(!toolbar.data("sourceOpened"))
{
clearTimeout(editorChangeTimer);
editorChangeTimer = setTimeout(postToSource,10);
}
})
// run to keyboard shortcuts
.keydown(function(e)
{
// if ctrl key is clicked
if(e.ctrlKey)
{
// check all toolbar buttons
for(var n = 0; n < buttons.length; n++)
{
// if this settings of this button is activated (is it true)
// if the keyed button with ctrl is same of hotkey of this button
if(vars[buttons[n].name] && e.keyCode == buttons[n].key.charCodeAt(0))
{
if(buttons[n].command!='' && buttons[n].command!='linkcreator')
selectionSet(buttons[n].command,null);
else if(buttons[n].command=='linkcreator')
selected2link();
return false;
}
}
}
})
// method of triggering to the highlight button
.bind("mouseup keyup",buttonEmphasize)
// the event of focus to the text field
.focus(function()
{
// if the focus method is added run the focus method
if($.isFunction(vars.focus))
vars.focus();
// add onfocus class
jQTE.addClass(vars.css+"_focused");
// prevent focus problem on opera
if(/opera/.test(thisBrowser))
{
var range = document.createRange();
range.selectNodeContents(editor[0]);
range.collapse(false);
var selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
})
// the event of focus out from the text field
.focusout(function()
{
// remove to highlights of all toolbar buttons
toolbutton.removeClass(emphasize);
// hide all menu-fields
styleFieldSwitch("",false);
formatFieldSwitch(false);
linktypeSwitch();
// if the blur method is added run the blur method
if($.isFunction(vars.blur))
vars.blur();
// remove onfocus class
jQTE.removeClass(vars.css+"_focused");
// show default text format
if($.isArray(vars.formats))
formatLabelView(vars.formats[0][1]);
});
// the event of key in the source field
thisElement
.bind("keydown keyup",function()
{
// export contents of the source to the text field
setTimeout(postToEditor,0);
// auto extension for the source field
$(this).height($(this)[0].scrollHeight);
// if the source field is empty, shorten to the source field
if($(this).val()=="")
$(this).height(0);
})
.focus(function()
{
// add onfocus class
jQTE.addClass(vars.css+"_focused");
})
.focusout(function()
{
// remove onfocus class
jQTE.removeClass(vars.css+"_focused");
});
});
};
})(jQuery);
\ No newline at end of file
<html>
<head>
<title>JQuery Text Editor Gadget</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="jqte/jquery-te-1.4.0.css" />
<script src="../../lib/jquery/jquery.js" type="text/javascript"></script>
<script src="../../renderjs.js" type="text/javascript"></script>
<script src="jqte/jquery-te-1.4.0.js" type="text/javascript"></script>
<script src="jqteditor.js" type="text/javascript"></script>
<link rel="http://www.renderjs.org/rel/interface"
href="http://www.renderjs.org/interface/editor"/>
</head>
<body>
<!--label for="textarea-b">Your advanced text editor:</label-->
<textarea id="textarea-b" class="ckeditor"></textarea>
</body>
</html>
/*global window, jQuery, rJS */
"use strict";
(function (window, $, rJS) {
var gk = rJS(window);
gk.declareMethod('setContent', function (value) {
// return rJS(this).context.find('textarea').val(escape_text(value));
return rJS(this).context.find('#textarea-b').jqteVal(value);
})
.declareMethod('getContent', function () {
return rJS(this).context.find('#textarea-b').val();
});
gk.ready(function () {
var g = rJS(this);
g.context.find("#textarea-b").jqte();
});
}(window, $, rJS))
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Office JS</title>
<link rel="stylesheet" href="../../lib/jqm/jquery.mobile.css" />
<script src="../../lib/jquery/jquery.js" type="text/javascript"></script>
<script src="../../renderjs.js" type="text/javascript"></script>
<script src="officejs.js" type="text/javascript"></script>
<script src="../../lib/jqm/jquery.mobile.js" type="text/javascript"></script>
<link rel="http://www.renderjs.org/rel/interface"
href="http://www.renderjs.org/interface/officejs"/>
</head>
<body>
<div data-role="page" id="index">
<!-- header -->
<div data-role="header">
<a href="#menu"
data-rel="panel"
data-role="button"
data-icon="bars"
data-iconpos="notext"
data-dismissable="false">Menu</a>
<h1>Office JS</h1>
</div>
<!-- panel -->
<div data-role="panel" id="menu" data-theme="a" class="bare_panel"></div>
<div class="catalog_location"></div>
<!-- content -->
<div data-role="content">
<!-- editor A -->
<div class="editor_a">
<div data-role="fieldcontain" class="editor_a_safe">
</div>
<div data-role="fieldcontain" class="editor_a">
</div>
</div>
<!-- editor B -->
<div class="editor_b">
<div data-role="fieldcontain" class="editor_b_safe">
</div>
<div data-role="fieldcontain" class="editor_b">
</div>
</div>
</div>
</div>
</body>
</html>
/*global window, jQuery, rJS */
"use strict";
(function (window, $, rJS) {
function attachIOToEditor(editor, io, id) {
editor.context.trigger('create');
io.context.trigger('create');
io.configureIO(id).done(function () {
io.configureDataSourceCallback(editor, editor.getContent);
io.getIO().done(function (data) {
editor.setContent(data);
});
});
}
rJS(window).ready(function () {
var g = rJS(this),
catalog_context = g.context.find(".catalog_location").last(),
editor_a_context = g.context.find(".editor_a").last(),
io_a_context = g.context.find(".editor_a_safe").last();
// editor_b_context = g.context.find(".editor_b").last(),
// io_b_context = g.context.find(".editor_b_safe").last();
;
// First, load the catalog gadget
g.declareGadget('./catalog.html', catalog_context).done(function (catalog) {
// Fetch the list of editor and io gadgets
// This is done in 2 different queries to the catalog
$.when(
catalog.allDocs(
{query: 'interface: "http://www.renderjs.org/interface/editor"'}),
catalog.allDocs(
{query: 'interface: "http://www.renderjs.org/interface/io"'}))
.done(function (editor_list, io_list) {
var panel_context = g.context.find(".bare_panel");
// Load 1 editor and 1 IO and plug them
$.when(
g.declareGadget(editor_list[0].path, editor_a_context),
g.declareGadget(io_list[0].path, io_a_context),
"officejs").done(attachIOToEditor);
// Fill the panel
$.each(editor_list, function(i, editor_definition) {
panel_context.append(
'<a href="#" data-role="button" data-icon="edit" ' +
'data-iconpos="left">' + editor_definition.title + '</a>');
panel_context.find('a').last().click(function () {
$.when(
g.declareGadget(editor_definition.path, editor_a_context),
g.declareGadget(io_list[0].path, io_a_context),
"officejs").done(attachIOToEditor);
});
});
panel_context.trigger('create');
});
});
// $.when(
// g.declareGadget('./jqteditor.html', editor_a_context),
// g.declareGadget('./io.html', io_a_context),
// "officejs_a").done(attachIOToEditor);
// $.when(
// g.declareGadget('./editor.html', editor_b_context),
// g.declareGadget('./io.html', io_b_context),
// "officejs_b").done(attachIOToEditor);
});
}(window, $, rJS))
/*
* Copyright 2013, Nexedi SA
* Released under the LGPL license.
* http://www.gnu.org/licenses/lgpl.html
*/
/**
* Provides some function to use complex queries with item list
*
* @module complex_queries
*/
var complex_queries;
(function () {
"use strict";
var to_export = {}, module_name = "complex_queries";
/**
* Add a secured (write permission denied) property to an object.
*
* @param {Object} object The object to fill
* @param {String} key The object key where to store the property
* @param {Any} value The value to store
*/
function _export(key, value) {
Object.defineProperty(to_export, key, {
"configurable": false,
"enumerable": true,
"writable": false,
"value": value
});
}
/**
* Parse a text request to a json query object tree
*
* @param {String} string The string to parse
* @return {Object} The json query tree
*/
function parseStringToObject(string) {
/*
Default template driver for JS/CC generated parsers running as
browser-based JavaScript/ECMAScript applications.
WARNING: This parser template will not run as console and has lesser
features for debugging than the console derivates for the
various JavaScript platforms.
Features:
- Parser trace messages
- Integrated panic-mode error recovery
Written 2007, 2008 by Jan Max Meyer, J.M.K S.F. Software Technologies
This is in the public domain.
*/
var NODEJS__dbg_withtrace = false;
var NODEJS__dbg_string = new String();
function __NODEJS_dbg_print( text )
{
NODEJS__dbg_string += text + "\n";
}
function __NODEJS_lex( info )
{
var state = 0;
var match = -1;
var match_pos = 0;
var start = 0;
var pos = info.offset + 1;
do
{
pos--;
state = 0;
match = -2;
start = pos;
if( info.src.length <= start )
return 19;
do
{
switch( state )
{
case 0:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 8 ) || ( info.src.charCodeAt( pos ) >= 10 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || info.src.charCodeAt( pos ) == 59 || ( info.src.charCodeAt( pos ) >= 63 && info.src.charCodeAt( pos ) <= 64 ) || ( info.src.charCodeAt( pos ) >= 66 && info.src.charCodeAt( pos ) <= 77 ) || ( info.src.charCodeAt( pos ) >= 80 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 9 ) state = 2;
else if( info.src.charCodeAt( pos ) == 40 ) state = 3;
else if( info.src.charCodeAt( pos ) == 41 ) state = 4;
else if( info.src.charCodeAt( pos ) == 60 || info.src.charCodeAt( pos ) == 62 ) state = 5;
else if( info.src.charCodeAt( pos ) == 34 ) state = 11;
else if( info.src.charCodeAt( pos ) == 79 ) state = 12;
else if( info.src.charCodeAt( pos ) == 32 ) state = 13;
else if( info.src.charCodeAt( pos ) == 61 ) state = 14;
else if( info.src.charCodeAt( pos ) == 65 ) state = 18;
else if( info.src.charCodeAt( pos ) == 78 ) state = 19;
else state = -1;
break;
case 1:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
else state = -1;
match = 10;
match_pos = pos;
break;
case 2:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
else state = -1;
match = 1;
match_pos = pos;
break;
case 3:
state = -1;
match = 3;
match_pos = pos;
break;
case 4:
state = -1;
match = 4;
match_pos = pos;
break;
case 5:
if( info.src.charCodeAt( pos ) == 61 ) state = 14;
else state = -1;
match = 11;
match_pos = pos;
break;
case 6:
state = -1;
match = 8;
match_pos = pos;
break;
case 7:
state = -1;
match = 9;
match_pos = pos;
break;
case 8:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
else state = -1;
match = 6;
match_pos = pos;
break;
case 9:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
else state = -1;
match = 5;
match_pos = pos;
break;
case 10:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
else state = -1;
match = 7;
match_pos = pos;
break;
case 11:
if( info.src.charCodeAt( pos ) == 34 ) state = 7;
else if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 33 ) || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 91 ) || ( info.src.charCodeAt( pos ) >= 93 && info.src.charCodeAt( pos ) <= 254 ) ) state = 11;
else if( info.src.charCodeAt( pos ) == 92 ) state = 15;
else state = -1;
break;
case 12:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 81 ) || ( info.src.charCodeAt( pos ) >= 83 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
else if( info.src.charCodeAt( pos ) == 82 ) state = 8;
else state = -1;
match = 10;
match_pos = pos;
break;
case 13:
state = -1;
match = 1;
match_pos = pos;
break;
case 14:
state = -1;
match = 11;
match_pos = pos;
break;
case 15:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 254 ) ) state = 11;
else state = -1;
break;
case 16:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 67 ) || ( info.src.charCodeAt( pos ) >= 69 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
else if( info.src.charCodeAt( pos ) == 68 ) state = 9;
else state = -1;
match = 10;
match_pos = pos;
break;
case 17:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 83 ) || ( info.src.charCodeAt( pos ) >= 85 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
else if( info.src.charCodeAt( pos ) == 84 ) state = 10;
else state = -1;
match = 10;
match_pos = pos;
break;
case 18:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 77 ) || ( info.src.charCodeAt( pos ) >= 79 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
else if( info.src.charCodeAt( pos ) == 78 ) state = 16;
else state = -1;
match = 10;
match_pos = pos;
break;
case 19:
if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 31 ) || info.src.charCodeAt( pos ) == 33 || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 39 ) || ( info.src.charCodeAt( pos ) >= 42 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 59 && info.src.charCodeAt( pos ) <= 78 ) || ( info.src.charCodeAt( pos ) >= 80 && info.src.charCodeAt( pos ) <= 254 ) ) state = 1;
else if( info.src.charCodeAt( pos ) == 58 ) state = 6;
else if( info.src.charCodeAt( pos ) == 79 ) state = 17;
else state = -1;
match = 10;
match_pos = pos;
break;
}
pos++;
}
while( state > -1 );
}
while( 1 > -1 && match == 1 );
if( match > -1 )
{
info.att = info.src.substr( start, match_pos - start );
info.offset = match_pos;
}
else
{
info.att = new String();
match = -1;
}
return match;
}
function __NODEJS_parse( src, err_off, err_la )
{
var sstack = new Array();
var vstack = new Array();
var err_cnt = 0;
var act;
var go;
var la;
var rval;
var parseinfo = new Function( "", "var offset; var src; var att;" );
var info = new parseinfo();
/* Pop-Table */
var pop_tab = new Array(
new Array( 0/* begin' */, 1 ),
new Array( 13/* begin */, 1 ),
new Array( 12/* search_text */, 1 ),
new Array( 12/* search_text */, 2 ),
new Array( 12/* search_text */, 3 ),
new Array( 14/* and_expression */, 1 ),
new Array( 14/* and_expression */, 3 ),
new Array( 15/* boolean_expression */, 2 ),
new Array( 15/* boolean_expression */, 1 ),
new Array( 16/* expression */, 3 ),
new Array( 16/* expression */, 2 ),
new Array( 16/* expression */, 1 ),
new Array( 17/* value */, 2 ),
new Array( 17/* value */, 1 ),
new Array( 18/* string */, 1 ),
new Array( 18/* string */, 1 )
);
/* Action-Table */
var act_tab = new Array(
/* State 0 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
/* State 1 */ new Array( 19/* "$" */,0 ),
/* State 2 */ new Array( 19/* "$" */,-1 ),
/* State 3 */ new Array( 6/* "OR" */,14 , 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 , 19/* "$" */,-2 , 4/* "RIGHT_PARENTHESE" */,-2 ),
/* State 4 */ new Array( 5/* "AND" */,16 , 19/* "$" */,-5 , 7/* "NOT" */,-5 , 3/* "LEFT_PARENTHESE" */,-5 , 8/* "COLUMN" */,-5 , 11/* "OPERATOR" */,-5 , 10/* "WORD" */,-5 , 9/* "STRING" */,-5 , 6/* "OR" */,-5 , 4/* "RIGHT_PARENTHESE" */,-5 ),
/* State 5 */ new Array( 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
/* State 6 */ new Array( 19/* "$" */,-8 , 7/* "NOT" */,-8 , 3/* "LEFT_PARENTHESE" */,-8 , 8/* "COLUMN" */,-8 , 11/* "OPERATOR" */,-8 , 10/* "WORD" */,-8 , 9/* "STRING" */,-8 , 6/* "OR" */,-8 , 5/* "AND" */,-8 , 4/* "RIGHT_PARENTHESE" */,-8 ),
/* State 7 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
/* State 8 */ new Array( 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
/* State 9 */ new Array( 19/* "$" */,-11 , 7/* "NOT" */,-11 , 3/* "LEFT_PARENTHESE" */,-11 , 8/* "COLUMN" */,-11 , 11/* "OPERATOR" */,-11 , 10/* "WORD" */,-11 , 9/* "STRING" */,-11 , 6/* "OR" */,-11 , 5/* "AND" */,-11 , 4/* "RIGHT_PARENTHESE" */,-11 ),
/* State 10 */ new Array( 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
/* State 11 */ new Array( 19/* "$" */,-13 , 7/* "NOT" */,-13 , 3/* "LEFT_PARENTHESE" */,-13 , 8/* "COLUMN" */,-13 , 11/* "OPERATOR" */,-13 , 10/* "WORD" */,-13 , 9/* "STRING" */,-13 , 6/* "OR" */,-13 , 5/* "AND" */,-13 , 4/* "RIGHT_PARENTHESE" */,-13 ),
/* State 12 */ new Array( 19/* "$" */,-14 , 7/* "NOT" */,-14 , 3/* "LEFT_PARENTHESE" */,-14 , 8/* "COLUMN" */,-14 , 11/* "OPERATOR" */,-14 , 10/* "WORD" */,-14 , 9/* "STRING" */,-14 , 6/* "OR" */,-14 , 5/* "AND" */,-14 , 4/* "RIGHT_PARENTHESE" */,-14 ),
/* State 13 */ new Array( 19/* "$" */,-15 , 7/* "NOT" */,-15 , 3/* "LEFT_PARENTHESE" */,-15 , 8/* "COLUMN" */,-15 , 11/* "OPERATOR" */,-15 , 10/* "WORD" */,-15 , 9/* "STRING" */,-15 , 6/* "OR" */,-15 , 5/* "AND" */,-15 , 4/* "RIGHT_PARENTHESE" */,-15 ),
/* State 14 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
/* State 15 */ new Array( 19/* "$" */,-3 , 4/* "RIGHT_PARENTHESE" */,-3 ),
/* State 16 */ new Array( 7/* "NOT" */,5 , 3/* "LEFT_PARENTHESE" */,7 , 8/* "COLUMN" */,8 , 11/* "OPERATOR" */,10 , 10/* "WORD" */,12 , 9/* "STRING" */,13 ),
/* State 17 */ new Array( 19/* "$" */,-7 , 7/* "NOT" */,-7 , 3/* "LEFT_PARENTHESE" */,-7 , 8/* "COLUMN" */,-7 , 11/* "OPERATOR" */,-7 , 10/* "WORD" */,-7 , 9/* "STRING" */,-7 , 6/* "OR" */,-7 , 5/* "AND" */,-7 , 4/* "RIGHT_PARENTHESE" */,-7 ),
/* State 18 */ new Array( 4/* "RIGHT_PARENTHESE" */,23 ),
/* State 19 */ new Array( 19/* "$" */,-10 , 7/* "NOT" */,-10 , 3/* "LEFT_PARENTHESE" */,-10 , 8/* "COLUMN" */,-10 , 11/* "OPERATOR" */,-10 , 10/* "WORD" */,-10 , 9/* "STRING" */,-10 , 6/* "OR" */,-10 , 5/* "AND" */,-10 , 4/* "RIGHT_PARENTHESE" */,-10 ),
/* State 20 */ new Array( 19/* "$" */,-12 , 7/* "NOT" */,-12 , 3/* "LEFT_PARENTHESE" */,-12 , 8/* "COLUMN" */,-12 , 11/* "OPERATOR" */,-12 , 10/* "WORD" */,-12 , 9/* "STRING" */,-12 , 6/* "OR" */,-12 , 5/* "AND" */,-12 , 4/* "RIGHT_PARENTHESE" */,-12 ),
/* State 21 */ new Array( 19/* "$" */,-4 , 4/* "RIGHT_PARENTHESE" */,-4 ),
/* State 22 */ new Array( 19/* "$" */,-6 , 7/* "NOT" */,-6 , 3/* "LEFT_PARENTHESE" */,-6 , 8/* "COLUMN" */,-6 , 11/* "OPERATOR" */,-6 , 10/* "WORD" */,-6 , 9/* "STRING" */,-6 , 6/* "OR" */,-6 , 4/* "RIGHT_PARENTHESE" */,-6 ),
/* State 23 */ new Array( 19/* "$" */,-9 , 7/* "NOT" */,-9 , 3/* "LEFT_PARENTHESE" */,-9 , 8/* "COLUMN" */,-9 , 11/* "OPERATOR" */,-9 , 10/* "WORD" */,-9 , 9/* "STRING" */,-9 , 6/* "OR" */,-9 , 5/* "AND" */,-9 , 4/* "RIGHT_PARENTHESE" */,-9 )
);
/* Goto-Table */
var goto_tab = new Array(
/* State 0 */ new Array( 13/* begin */,1 , 12/* search_text */,2 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ),
/* State 1 */ new Array( ),
/* State 2 */ new Array( ),
/* State 3 */ new Array( 12/* search_text */,15 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ),
/* State 4 */ new Array( ),
/* State 5 */ new Array( 16/* expression */,17 , 17/* value */,9 , 18/* string */,11 ),
/* State 6 */ new Array( ),
/* State 7 */ new Array( 12/* search_text */,18 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ),
/* State 8 */ new Array( 16/* expression */,19 , 17/* value */,9 , 18/* string */,11 ),
/* State 9 */ new Array( ),
/* State 10 */ new Array( 18/* string */,20 ),
/* State 11 */ new Array( ),
/* State 12 */ new Array( ),
/* State 13 */ new Array( ),
/* State 14 */ new Array( 12/* search_text */,21 , 14/* and_expression */,3 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ),
/* State 15 */ new Array( ),
/* State 16 */ new Array( 14/* and_expression */,22 , 15/* boolean_expression */,4 , 16/* expression */,6 , 17/* value */,9 , 18/* string */,11 ),
/* State 17 */ new Array( ),
/* State 18 */ new Array( ),
/* State 19 */ new Array( ),
/* State 20 */ new Array( ),
/* State 21 */ new Array( ),
/* State 22 */ new Array( ),
/* State 23 */ new Array( )
);
/* Symbol labels */
var labels = new Array(
"begin'" /* Non-terminal symbol */,
"WHITESPACE" /* Terminal symbol */,
"WHITESPACE" /* Terminal symbol */,
"LEFT_PARENTHESE" /* Terminal symbol */,
"RIGHT_PARENTHESE" /* Terminal symbol */,
"AND" /* Terminal symbol */,
"OR" /* Terminal symbol */,
"NOT" /* Terminal symbol */,
"COLUMN" /* Terminal symbol */,
"STRING" /* Terminal symbol */,
"WORD" /* Terminal symbol */,
"OPERATOR" /* Terminal symbol */,
"search_text" /* Non-terminal symbol */,
"begin" /* Non-terminal symbol */,
"and_expression" /* Non-terminal symbol */,
"boolean_expression" /* Non-terminal symbol */,
"expression" /* Non-terminal symbol */,
"value" /* Non-terminal symbol */,
"string" /* Non-terminal symbol */,
"$" /* Terminal symbol */
);
info.offset = 0;
info.src = src;
info.att = new String();
if( !err_off )
err_off = new Array();
if( !err_la )
err_la = new Array();
sstack.push( 0 );
vstack.push( 0 );
la = __NODEJS_lex( info );
while( true )
{
act = 25;
for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 )
{
if( act_tab[sstack[sstack.length-1]][i] == la )
{
act = act_tab[sstack[sstack.length-1]][i+1];
break;
}
}
if( NODEJS__dbg_withtrace && sstack.length > 0 )
{
__NODEJS_dbg_print( "\nState " + sstack[sstack.length-1] + "\n" +
"\tLookahead: " + labels[la] + " (\"" + info.att + "\")\n" +
"\tAction: " + act + "\n" +
"\tSource: \"" + info.src.substr( info.offset, 30 ) + ( ( info.offset + 30 < info.src.length ) ?
"..." : "" ) + "\"\n" +
"\tStack: " + sstack.join() + "\n" +
"\tValue stack: " + vstack.join() + "\n" );
}
//Panic-mode: Try recovery when parse-error occurs!
if( act == 25 )
{
if( NODEJS__dbg_withtrace )
__NODEJS_dbg_print( "Error detected: There is no reduce or shift on the symbol " + labels[la] );
err_cnt++;
err_off.push( info.offset - info.att.length );
err_la.push( new Array() );
for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 )
err_la[err_la.length-1].push( labels[act_tab[sstack[sstack.length-1]][i]] );
//Remember the original stack!
var rsstack = new Array();
var rvstack = new Array();
for( var i = 0; i < sstack.length; i++ )
{
rsstack[i] = sstack[i];
rvstack[i] = vstack[i];
}
while( act == 25 && la != 19 )
{
if( NODEJS__dbg_withtrace )
__NODEJS_dbg_print( "\tError recovery\n" +
"Current lookahead: " + labels[la] + " (" + info.att + ")\n" +
"Action: " + act + "\n\n" );
if( la == -1 )
info.offset++;
while( act == 25 && sstack.length > 0 )
{
sstack.pop();
vstack.pop();
if( sstack.length == 0 )
break;
act = 25;
for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 )
{
if( act_tab[sstack[sstack.length-1]][i] == la )
{
act = act_tab[sstack[sstack.length-1]][i+1];
break;
}
}
}
if( act != 25 )
break;
for( var i = 0; i < rsstack.length; i++ )
{
sstack.push( rsstack[i] );
vstack.push( rvstack[i] );
}
la = __NODEJS_lex( info );
}
if( act == 25 )
{
if( NODEJS__dbg_withtrace )
__NODEJS_dbg_print( "\tError recovery failed, terminating parse process..." );
break;
}
if( NODEJS__dbg_withtrace )
__NODEJS_dbg_print( "\tError recovery succeeded, continuing" );
}
/*
if( act == 25 )
break;
*/
//Shift
if( act > 0 )
{
if( NODEJS__dbg_withtrace )
__NODEJS_dbg_print( "Shifting symbol: " + labels[la] + " (" + info.att + ")" );
sstack.push( act );
vstack.push( info.att );
la = __NODEJS_lex( info );
if( NODEJS__dbg_withtrace )
__NODEJS_dbg_print( "\tNew lookahead symbol: " + labels[la] + " (" + info.att + ")" );
}
//Reduce
else
{
act *= -1;
if( NODEJS__dbg_withtrace )
__NODEJS_dbg_print( "Reducing by producution: " + act );
rval = void(0);
if( NODEJS__dbg_withtrace )
__NODEJS_dbg_print( "\tPerforming semantic action..." );
switch( act )
{
case 0:
{
rval = vstack[ vstack.length - 1 ];
}
break;
case 1:
{
result = vstack[ vstack.length - 1 ];
}
break;
case 2:
{
rval = vstack[ vstack.length - 1 ];
}
break;
case 3:
{
rval = mkComplexQuery('OR',[vstack[ vstack.length - 2 ],vstack[ vstack.length - 1 ]]);
}
break;
case 4:
{
rval = mkComplexQuery('OR',[vstack[ vstack.length - 3 ],vstack[ vstack.length - 1 ]]);
}
break;
case 5:
{
rval = vstack[ vstack.length - 1 ] ;
}
break;
case 6:
{
rval = mkComplexQuery('AND',[vstack[ vstack.length - 3 ],vstack[ vstack.length - 1 ]]);
}
break;
case 7:
{
rval = mkNotQuery(vstack[ vstack.length - 1 ]);
}
break;
case 8:
{
rval = vstack[ vstack.length - 1 ];
}
break;
case 9:
{
rval = vstack[ vstack.length - 2 ];
}
break;
case 10:
{
simpleQuerySetKey(vstack[ vstack.length - 1 ],vstack[ vstack.length - 2 ].split(':').slice(0,-1).join(':')); rval = vstack[ vstack.length - 1 ];
}
break;
case 11:
{
rval = vstack[ vstack.length - 1 ];
}
break;
case 12:
{
vstack[ vstack.length - 1 ].operator = vstack[ vstack.length - 2 ] ; rval = vstack[ vstack.length - 1 ];
}
break;
case 13:
{
rval = vstack[ vstack.length - 1 ];
}
break;
case 14:
{
rval = mkSimpleQuery('',vstack[ vstack.length - 1 ]);
}
break;
case 15:
{
rval = mkSimpleQuery('',vstack[ vstack.length - 1 ].split('"').slice(1,-1).join('"'));
}
break;
}
if( NODEJS__dbg_withtrace )
__NODEJS_dbg_print( "\tPopping " + pop_tab[act][1] + " off the stack..." );
for( var i = 0; i < pop_tab[act][1]; i++ )
{
sstack.pop();
vstack.pop();
}
go = -1;
for( var i = 0; i < goto_tab[sstack[sstack.length-1]].length; i+=2 )
{
if( goto_tab[sstack[sstack.length-1]][i] == pop_tab[act][0] )
{
go = goto_tab[sstack[sstack.length-1]][i+1];
break;
}
}
if( act == 0 )
break;
if( NODEJS__dbg_withtrace )
__NODEJS_dbg_print( "\tPushing non-terminal " + labels[ pop_tab[act][0] ] );
sstack.push( go );
vstack.push( rval );
}
if( NODEJS__dbg_withtrace )
{
alert( NODEJS__dbg_string );
NODEJS__dbg_string = new String();
}
}
if( NODEJS__dbg_withtrace )
{
__NODEJS_dbg_print( "\nParse complete." );
alert( NODEJS__dbg_string );
}
return err_cnt;
}
var arrayExtend = function () {
var j, i, newlist = [], list_list = arguments;
for (j = 0; j < list_list.length; j += 1) {
for (i = 0; i < list_list[j].length; i += 1) {
newlist.push(list_list[j][i]);
}
}
return newlist;
}, mkSimpleQuery = function (key, value, operator) {
return {"type": "simple", "operator": "=", "key": key, "value": value};
}, mkNotQuery = function (query) {
if (query.operator === "NOT") {
return query.query_list[0];
}
return {"type": "complex", "operator": "NOT", "query_list": [query]};
}, mkComplexQuery = function (operator, query_list) {
var i, query_list2 = [];
for (i = 0; i < query_list.length; i += 1) {
if (query_list[i].operator === operator) {
query_list2 = arrayExtend(query_list2, query_list[i].query_list);
} else {
query_list2.push(query_list[i]);
}
}
return {type:"complex",operator:operator,query_list:query_list2};
}, simpleQuerySetKey = function (query, key) {
var i;
if (query.type === "complex") {
for (i = 0; i < query.query_list.length; ++i) {
simpleQuerySetKey (query.query_list[i],key);
}
return true;
}
if (query.type === "simple" && !query.key) {
query.key = key;
return true;
}
return false;
},
error_offsets = [],
error_lookaheads = [],
error_count = 0,
result;
if ((error_count = __NODEJS_parse(string, error_offsets, error_lookaheads)) > 0) {
var i;
for (i = 0; i < error_count; i += 1) {
throw new Error("Parse error near \"" +
string.substr(error_offsets[i]) +
"\", expecting \"" +
error_lookaheads[i].join() + "\"");
}
}
return result;
} // parseStringToObject
_export('parseStringToObject', parseStringToObject);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global _export: true */
/**
* Escapes regexp special chars from a string.
*
* @param {String} string The string to escape
* @return {String} The escaped string
*/
function stringEscapeRegexpCharacters(string) {
if (typeof string === "string") {
return string.replace(/([\\\.\$\[\]\(\)\{\}\^\?\*\+\-])/g, "\\$1");
}
throw new TypeError("complex_queries.stringEscapeRegexpCharacters(): " +
"Argument no 1 is not of type 'string'");
}
_export("stringEscapeRegexpCharacters", stringEscapeRegexpCharacters);
/**
* A sort function to sort items by key
*
* @param {String} key The key to sort on
* @param {String} [way="ascending"] 'ascending' or 'descending'
* @return {Function} The sort function
*/
function sortFunction(key, way) {
if (way === 'descending') {
return function (a, b) {
return a[key] < b[key] ? 1 : a[key] > b[key] ? -1 : 0;
};
}
if (way === 'ascending') {
return function (a, b) {
return a[key] > b[key] ? 1 : a[key] < b[key] ? -1 : 0;
};
}
throw new TypeError("complex_queries.sortFunction(): " +
"Argument 2 must be 'ascending' or 'descending'");
}
/**
* Clones all native object in deep. Managed types: Object, Array, String,
* Number, Boolean, null.
*
* @param {A} object The object to clone
* @return {A} The cloned object
*/
function deepClone(object) {
var i, cloned;
if (Array.isArray(object)) {
cloned = [];
for (i = 0; i < object.length; i += 1) {
cloned[i] = deepClone(object[i]);
}
return cloned;
}
if (typeof object === "object") {
cloned = {};
for (i in object) {
if (object.hasOwnProperty(i)) {
cloned[i] = deepClone(object[i]);
}
}
return cloned;
}
return object;
}
/**
* Inherits the prototype methods from one constructor into another. The
* prototype of `constructor` will be set to a new object created from
* `superConstructor`.
*
* @param {Function} constructor The constructor which inherits the super one
* @param {Function} superConstructor The super constructor
*/
function inherits(constructor, superConstructor) {
constructor.super_ = superConstructor;
constructor.prototype = Object.create(superConstructor.prototype, {
"constructor": {
"configurable": true,
"enumerable": false,
"writable": true,
"value": constructor
}
});
}
/**
* Does nothing
*/
function emptyFunction() {}
/**
* Filter a list of items, modifying them to select only wanted keys. If
* `clone` is true, then the method will act on a cloned list.
*
* @param {Array} select_option Key list to keep
* @param {Array} list The item list to filter
* @param {Boolean} [clone=false] If true, modifies a clone of the list
* @return {Array} The filtered list
*/
function select(select_option, list, clone) {
var i, j, new_item;
if (!Array.isArray(select_option)) {
throw new TypeError("complex_queries.select(): " +
"Argument 1 is not of type Array");
}
if (!Array.isArray(list)) {
throw new TypeError("complex_queries.select(): " +
"Argument 2 is not of type Array");
}
if (clone === true) {
list = deepClone(list);
}
for (i = 0; i < list.length; i += 1) {
new_item = {};
for (j = 0; j < select_option.length; j += 1) {
new_item[select_option[j]] = list[i][select_option[j]];
}
for (j in new_item) {
if (new_item.hasOwnProperty(j)) {
list[i] = new_item;
break;
}
}
}
return list;
}
_export('select', select);
/**
* Sort a list of items, according to keys and directions. If `clone` is true,
* then the method will act on a cloned list.
*
* @param {Array} sort_on_option List of couples [key, direction]
* @param {Array} list The item list to sort
* @param {Boolean} [clone=false] If true, modifies a clone of the list
* @return {Array} The filtered list
*/
function sortOn(sort_on_option, list, clone) {
var sort_index;
if (!Array.isArray(sort_on_option)) {
throw new TypeError("complex_queries.sortOn(): " +
"Argument 1 is not of type 'array'");
}
if (clone) {
list = deepClone(list);
}
for (sort_index = sort_on_option.length - 1; sort_index >= 0;
sort_index -= 1) {
list.sort(sortFunction(
sort_on_option[sort_index][0],
sort_on_option[sort_index][1]
));
}
return list;
}
_export('sortOn', sortOn);
/**
* Limit a list of items, according to index and length. If `clone` is true,
* then the method will act on a cloned list.
*
* @param {Array} limit_option A couple [from, length]
* @param {Array} list The item list to limit
* @param {Boolean} [clone=false] If true, modifies a clone of the list
* @return {Array} The filtered list
*/
function limit(limit_option, list, clone) {
if (!Array.isArray(limit_option)) {
throw new TypeError("complex_queries.limit(): " +
"Argument 1 is not of type 'array'");
}
if (!Array.isArray(list)) {
throw new TypeError("complex_queries.limit(): " +
"Argument 2 is not of type 'array'");
}
if (clone) {
list = deepClone(list);
}
list.splice(0, limit_option[0]);
if (limit_option[1]) {
list.splice(limit_option[1]);
}
return list;
}
_export('limit', limit);
/**
* Convert a search text to a regexp.
*
* @param {String} string The string to convert
* @param {String} [wildcard_character=undefined] The wildcard chararter
* @return {RegExp} The search text regexp
*/
function convertStringToRegExp(string, wildcard_character) {
if (typeof string !== 'string') {
throw new TypeError("complex_queries.convertStringToRegExp(): " +
"Argument 1 is not of type 'string'");
}
if (wildcard_character === undefined ||
wildcard_character === null || wildcard_character === '') {
return new RegExp("^" + stringEscapeRegexpCharacters(string) + "$");
}
if (typeof wildcard_character !== 'string' || wildcard_character.length > 1) {
throw new TypeError("complex_queries.convertStringToRegExp(): " +
"Optional argument 2 must be a string of length <= 1");
}
return new RegExp("^" + stringEscapeRegexpCharacters(string).replace(
new RegExp(stringEscapeRegexpCharacters(wildcard_character), 'g'),
'.*'
) + "$");
}
_export('convertStringToRegExp', convertStringToRegExp);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global _export: true, ComplexQuery: true, SimpleQuery: true, Query: true,
parseStringToObject: true */
var query_class_dict = {};
/**
* Provides static methods to create Query object
*
* @class QueryFactory
*/
function QueryFactory() {}
/**
* Creates Query object from a search text string or a serialized version
* of a Query.
*
* @method create
* @static
* @param {Object,String} object The search text or the serialized version
* of a Query
* @return {Query} A Query object
*/
QueryFactory.create = function (object) {
if (object === "") {
return new Query();
}
if (typeof object === "string") {
object = parseStringToObject(object);
}
if (typeof (object || {}).type === "string" &&
query_class_dict[object.type]) {
return new query_class_dict[object.type](object);
}
throw new TypeError("QueryFactory.create(): " +
"Argument 1 is not a search text or a parsable object");
};
_export("QueryFactory", QueryFactory);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global parseStringToObject: true, emptyFunction: true, sortOn: true, limit:
true, select: true, _export: true, stringEscapeRegexpCharacters: true,
deepClone: true */
/**
* The query to use to filter a list of objects.
* This is an abstract class.
*
* @class Query
* @constructor
*/
function Query() {
/**
* Called before parsing the query. Must be overridden!
*
* @method onParseStart
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
this.onParseStart = emptyFunction;
/**
* Called when parsing a simple query. Must be overridden!
*
* @method onParseSimpleQuery
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
this.onParseSimpleQuery = emptyFunction;
/**
* Called when parsing a complex query. Must be overridden!
*
* @method onParseComplexQuery
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
this.onParseComplexQuery = emptyFunction;
/**
* Called after parsing the query. Must be overridden!
*
* @method onParseEnd
* @param {Object} object The object shared in the parse process
* @param {Object} option Some option gave in parse()
*/
this.onParseEnd = emptyFunction;
}
/**
* Filter the item list with matching item only
*
* @method exec
* @param {Array} item_list The list of object
* @param {Object} [option] Some operation option
* @param {String} [option.wildcard_character="%"] The wildcard character
* @param {Array} [option.select_list] A object keys to retrieve
* @param {Array} [option.sort_on] Couples of object keys and "ascending"
* or "descending"
* @param {Array} [option.limit] Couple of integer, first is an index and
* second is the length.
*/
Query.prototype.exec = function (item_list, option) {
var i = 0;
if (!Array.isArray(item_list)) {
throw new TypeError("Query().exec(): Argument 1 is not of type 'array'");
}
if (option === undefined) {
option = {};
}
if (typeof option !== 'object') {
throw new TypeError("Query().exec(): " +
"Optional argument 2 is not of type 'object'");
}
if (option.wildcard_character === undefined) {
option.wildcard_character = '%';
}
while (i < item_list.length) {
if (!this.match(item_list[i], option.wildcard_character)) {
item_list.splice(i, 1);
} else {
i += 1;
}
}
if (option.sort_on) {
sortOn(option.sort_on, item_list);
}
if (option.limit) {
limit(option.limit, item_list);
}
select(option.select_list || [], item_list);
};
/**
* Test if an item matches this query
*
* @method match
* @param {Object} item The object to test
* @return {Boolean} true if match, false otherwise
*/
Query.prototype.match = function (item, wildcard_character) {
return true;
};
/**
* Browse the Query in deep calling parser method in each step.
*
* `onParseStart` is called first, on end `onParseEnd` is called.
* It starts from the simple queries at the bottom of the tree calling the
* parser method `onParseSimpleQuery`, and go up calling the
* `onParseComplexQuery` method.
*
* @method parse
* @param {Object} option Any options you want (except 'parsed')
* @return {Any} The parse result
*/
Query.prototype.parse = function (option) {
var that = this, object;
/**
* The recursive parser.
*
* @param {Object} object The object shared in the parse process
* @param {Object} options Some options usable in the parseMethods
* @return {Any} The parser result
*/
function recParse(object, option) {
var i, query = object.parsed;
if (query.type === "complex") {
for (i = 0; i < query.query_list.length; i += 1) {
object.parsed = query.query_list[i];
recParse(object, option);
query.query_list[i] = object.parsed;
}
object.parsed = query;
that.onParseComplexQuery(object, option);
} else if (query.type === "simple") {
that.onParseSimpleQuery(object, option);
}
}
object = {"parsed": JSON.parse(JSON.stringify(that.serialized()))};
that.onParseStart(object, option);
recParse(object, option);
that.onParseEnd(object, option);
return object.parsed;
};
/**
* Convert this query to a parsable string.
*
* @method toString
* @return {String} The string version of this query
*/
Query.prototype.toString = function () {
return "";
};
/**
* Convert this query to an jsonable object in order to be remake thanks to
* QueryFactory class.
*
* @method serialized
* @return {Object} The jsonable object
*/
Query.prototype.serialized = function () {
return undefined;
};
_export("Query", Query);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global Query: true, inherits: true, query_class_dict: true, _export: true,
convertStringToRegExp: true */
/**
* The SimpleQuery inherits from Query, and compares one metadata value
*
* @class SimpleQuery
* @extends Query
* @param {Object} [spec={}] The specifications
* @param {String} [spec.operator="="] The compare method to use
* @param {String} spec.key The metadata key
* @param {String} spec.value The value of the metadata to compare
*/
function SimpleQuery(spec) {
Query.call(this);
/**
* Operator to use to compare object values
*
* @attribute operator
* @type String
* @default "="
* @optional
*/
this.operator = spec.operator || "=";
/**
* Key of the object which refers to the value to compare
*
* @attribute key
* @type String
*/
this.key = spec.key;
/**
* Value is used to do the comparison with the object value
*
* @attribute value
* @type String
*/
this.value = spec.value;
}
inherits(SimpleQuery, Query);
/**
* #crossLink "Query/match:method"
*/
SimpleQuery.prototype.match = function (item, wildcard_character) {
return this[this.operator](item[this.key], this.value, wildcard_character);
};
/**
* #crossLink "Query/toString:method"
*/
SimpleQuery.prototype.toString = function () {
return (this.key ? this.key + ": " : "") + (this.operator || "=") + ' "' +
this.value + '"';
};
/**
* #crossLink "Query/serialized:method"
*/
SimpleQuery.prototype.serialized = function () {
return {
"type": "simple",
"operator": this.operator,
"key": this.key,
"value": this.value
};
};
/**
* Comparison operator, test if this query value matches the item value
*
* @method =
* @param {String} object_value The value to compare
* @param {String} comparison_value The comparison value
* @param {String} wildcard_character The wildcard_character
* @return {Boolean} true if match, false otherwise
*/
SimpleQuery.prototype["="] = function (object_value, comparison_value,
wildcard_character) {
return convertStringToRegExp(
comparison_value.toString(),
wildcard_character || "%"
).test(object_value.toString());
};
/**
* Comparison operator, test if this query value does not match the item value
*
* @method !=
* @param {String} object_value The value to compare
* @param {String} comparison_value The comparison value
* @param {String} wildcard_character The wildcard_character
* @return {Boolean} true if not match, false otherwise
*/
SimpleQuery.prototype["!="] = function (object_value, comparison_value,
wildcard_character) {
return !convertStringToRegExp(
comparison_value.toString(),
wildcard_character || "%"
).test(object_value.toString());
};
/**
* Comparison operator, test if this query value is lower than the item value
*
* @method <
* @param {Number, String} object_value The value to compare
* @param {Number, String} comparison_value The comparison value
* @return {Boolean} true if lower, false otherwise
*/
SimpleQuery.prototype["<"] = function (object_value, comparison_value) {
return object_value < comparison_value;
};
/**
* Comparison operator, test if this query value is equal or lower than the
* item value
*
* @method <=
* @param {Number, String} object_value The value to compare
* @param {Number, String} comparison_value The comparison value
* @return {Boolean} true if equal or lower, false otherwise
*/
SimpleQuery.prototype["<="] = function (object_value, comparison_value) {
return object_value <= comparison_value;
};
/**
* Comparison operator, test if this query value is greater than the item
* value
*
* @method >
* @param {Number, String} object_value The value to compare
* @param {Number, String} comparison_value The comparison value
* @return {Boolean} true if greater, false otherwise
*/
SimpleQuery.prototype[">"] = function (object_value, comparison_value) {
return object_value > comparison_value;
};
/**
* Comparison operator, test if this query value is equal or greater than the
* item value
*
* @method >=
* @param {Number, String} object_value The value to compare
* @param {Number, String} comparison_value The comparison value
* @return {Boolean} true if equal or greater, false otherwise
*/
SimpleQuery.prototype[">="] = function (object_value, comparison_value) {
return object_value >= comparison_value;
};
query_class_dict.simple = SimpleQuery;
_export("SimpleQuery", SimpleQuery);
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global Query: true, query_class_dict: true, inherits: true,
_export: true, QueryFactory: true */
/**
* The ComplexQuery inherits from Query, and compares one or several metadata
* values.
*
* @class ComplexQuery
* @extends Query
* @param {Object} [spec={}] The specifications
* @param {String} [spec.operator="AND"] The compare method to use
* @param {String} spec.key The metadata key
* @param {String} spec.value The value of the metadata to compare
*/
function ComplexQuery(spec) {
Query.call(this);
/**
* Logical operator to use to compare object values
*
* @attribute operator
* @type String
* @default "AND"
* @optional
*/
this.operator = spec.operator || "AND";
/**
* The sub Query list which are used to query an item.
*
* @attribute query_list
* @type Array
* @default []
* @optional
*/
this.query_list = spec.query_list || [];
this.query_list = this.query_list.map(QueryFactory.create);
}
inherits(ComplexQuery, Query);
/**
* #crossLink "Query/match:method"
*/
ComplexQuery.prototype.match = function (item, wildcard_character) {
return this[this.operator](item, wildcard_character);
};
/**
* #crossLink "Query/toString:method"
*/
ComplexQuery.prototype.toString = function () {
var str_list = ["("], this_operator = this.operator;
this.query_list.forEach(function (query) {
str_list.push(query.toString());
str_list.push(this_operator);
});
str_list.pop(); // remove last operator
str_list.push(")");
return str_list.join(" ");
};
/**
* #crossLink "Query/serialized:method"
*/
ComplexQuery.prototype.serialized = function () {
var s = {
"type": "complex",
"operator": this.operator,
"query_list": []
};
this.query_list.forEach(function (query) {
s.query_list.push(query.serialized());
});
return s;
};
/**
* Comparison operator, test if all sub queries match the
* item value
*
* @method AND
* @param {Object} item The item to match
* @param {String} wildcard_character The wildcard character
* @return {Boolean} true if all match, false otherwise
*/
ComplexQuery.prototype.AND = function (item, wildcard_character) {
var i;
for (i = 0; i < this.query_list.length; i += 1) {
if (!this.query_list[i].match(item, wildcard_character)) {
return false;
}
}
return true;
};
/**
* Comparison operator, test if one of the sub queries matches the
* item value
*
* @method OR
* @param {Object} item The item to match
* @param {String} wildcard_character The wildcard character
* @return {Boolean} true if one match, false otherwise
*/
ComplexQuery.prototype.OR = function (item, wildcard_character) {
var i;
for (i = 0; i < this.query_list.length; i += 1) {
if (this.query_list[i].match(item, wildcard_character)) {
return true;
}
}
return false;
};
/**
* Comparison operator, test if the sub query does not match the
* item value
*
* @method NOT
* @param {Object} item The item to match
* @param {String} wildcard_character The wildcard character
* @return {Boolean} true if one match, false otherwise
*/
ComplexQuery.prototype.NOT = function (item, wildcard_character) {
return !this.query_list[0].match(item, wildcard_character);
};
query_class_dict.complex = ComplexQuery;
_export("ComplexQuery", ComplexQuery);
if (typeof define === "function" && define.amd) {
define(to_export);
} else if (typeof window === "object") {
Object.defineProperty(window, module_name, {
configurable: false,
enumerable: true,
writable: false,
value: to_export
});
} else if (typeof exports === "object") {
var i;
for (i in to_export) {
if (to_export.hasOwnProperty(i)) {
exports[i] = to_export[i];
}
}
} else {
complex_queries = to_export;
}
}());
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global jIO: true, $: true, btoa: true */
// test here: http://enable-cors.org/
//http://metajack.im/2010/01/19/crossdomain-ajax-for-xmpp-http-binding-made-easy
jIO.addStorageType('dav', function (spec, my) {
spec = spec || {};
var that, priv, super_serialized;
that = my.basicStorage(spec, my);
priv = {};
super_serialized = that.serialized;
priv.secureDocId = function (string) {
var split = string.split('/'),
i;
if (split[0] === '') {
split = split.slice(1);
}
for (i = 0; i < split.length; i += 1) {
if (split[i] === '') {
return '';
}
}
return split.join('%2F');
};
priv.convertSlashes = function (string) {
return string.split('/').join('%2F');
};
priv.restoreSlashes = function (string) {
return string.split('%2F').join('/');
};
/**
* Checks if an object has no enumerable keys
* @method objectIsEmpty
* @param {object} obj The object
* @return {boolean} true if empty, else false
*/
priv.objectIsEmpty = function (obj) {
var k;
for (k in obj) {
if (obj.hasOwnProperty(k)) {
return false;
}
}
return true;
};
// ==================== Attributes ====================
priv.username = spec.username || '';
priv.secured_username = priv.convertSlashes(priv.username);
priv.password = spec.password || '';
priv.url = spec.url || '';
that.serialized = function () {
var o = super_serialized();
o.username = priv.username;
o.url = priv.url;
o.password = priv.password;
return o;
};
priv.newAsyncModule = function () {
var async = {};
async.call = function (obj, function_name, arglist) {
obj._wait = obj._wait || {};
if (obj._wait[function_name]) {
obj._wait[function_name] -= 1;
return function () {};
}
// ok if undef or 0
arglist = arglist || [];
return obj[function_name].apply(obj[function_name], arglist);
};
async.neverCall = function (obj, function_name) {
obj._wait = obj._wait || {};
obj._wait[function_name] = -1;
};
async.wait = function (obj, function_name, times) {
obj._wait = obj._wait || {};
obj._wait[function_name] = times;
};
async.end = function () {
async.call = function () {};
};
return async;
};
/**
* Checks if a browser supports cors (cross domain ajax requests)
* @method checkCors
* @return {boolean} true if supported, else false
*/
priv.checkCors = function () {
return $.support.cors;
};
/**
* Replaces last "." with "_." in document filenames
* @method underscoreFileExtenisons
* @param {string} url url to clean up
* @return {string} clean_url cleaned up URL
*/
priv.underscoreFileExtenisons = function (url) {
var clean_url = url.replace(/,\s(\w+)$/, "_.$1");
return clean_url;
};
priv.restoreDots = function (url) {
var clean_url = url.replace(/_\./g, '.');
return clean_url;
};
// wedDav methods rfc4918 (short summary)
// COPY Reproduces single resources (files) and collections (directory
// trees). Will overwrite files (if specified by request) but will
// respond 209 (Conflict) if it would overwrite a tree
// DELETE deletes files and directory trees
// GET just the vanilla HTTP/1.1 behaviour
// HEAD ditto
// LOCK locks a resources
// MKCOL creates a directory
// MOVE Moves (rename or copy) a file or a directory tree. Will
// 'overwrite' files (if specified by the request) but will respond
// 209 (Conflict) if it would overwrite a tree.
// OPTIONS If WebDAV is enabled and available for the path this reports the
// WebDAV extension methods
// PROPFIND Retrieves the requested file characteristics, DAV lock status
// and 'dead' properties for individual files, a directory and its
// child files, or a directory tree
// PROPPATCHset and remove 'dead' meta-data properties
// PUT Update or create resource or collections
// UNLOCK unlocks a resource
// Notes: all Ajax requests should be CORS (cross-domain)
// adding custom headers triggers preflight OPTIONS request!
// http://remysharp.com/2011/04/21/getting-cors-working/
priv.putOrPost = function (command, type) {
var docid = command.getDocId(),
secured_docid,
url;
// no docId
if (!(typeof docid === "string" && docid !== "")) {
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Cannot create document which id is undefined",
"reason": "Document id is undefined"
});
return;
}
// no cross domain ajax
if (priv.checkCors === false) {
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Browser does not support cross domain ajax requests",
"reason": "cors is undefined"
});
return;
}
secured_docid = priv.secureDocId(command.getDocId());
url = priv.url + '/' + priv.underscoreFileExtenisons(secured_docid);
// see if the document exists
$.ajax({
url: url + '?_=' + Date.now(),
type: "GET",
async: true,
dataType: 'text',
crossdomain : true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
// xhrFields: {withCredentials: 'true'},
success: function () {
if (type === 'POST') {
// POST the document already exists
that.error({
"status": 409,
"statusText": "Conflicts",
"error": "conflicts",
"message": "Cannot create a new document",
"reason": "Document already exists"
});
return;
}
// PUT update document
$.ajax({
url: url,
type: type,
data: JSON.stringify(command.getDoc()),
async: true,
crossdomain: true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
// xhrFields: {withCredentials: 'true'},
success: function () {
that.success({
ok: true,
id: command.getDocId()
});
},
error: function () {
that.error({
"status": 409,
"statusText": "Conflicts",
"error": "conflicts",
"message": "Cannot modify document",
"reason": "Error trying to write to remote storage"
});
}
});
},
error: function (err) {
// Firefox returns 0 instead of 404 on CORS?
if (err.status === 404 || err.status === 0) {
$.ajax({
url: url,
type: 'PUT',
data: JSON.stringify(command.getDoc()),
async: true,
crossdomain: true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
// xhrFields: {withCredentials: 'true'},
success: function () {
that.success({
ok: true,
id: command.getDocId()
});
},
error: function () {
that.error({
"status": 409,
"statusText": "Conflicts",
"error": "conflicts",
"message": "Cannot modify document",
"reason": "Error trying to write to remote storage"
});
}
});
} else {
// error accessing remote storage
that.error({
"status": err.status,
"statusText": err.statusText,
"error": "error",
"message": err.message,
"reason": "Failed to access remote storage"
});
}
}
});
};
/**
* Creates a new document
* @method post
* @param {object} command The JIO command
*/
that.post = function (command) {
priv.putOrPost(command, 'POST');
};
/**
* Creates or updates a document
* @method put
* @param {object} command The JIO command
*/
that.put = function (command) {
priv.putOrPost(command, 'PUT');
};
/**
* Add an attachment to a document
* @method putAttachment
* @param {object} command The JIO command
*/
that.putAttachment = function (command) {
var docid = command.getDocId(),
doc,
url,
secured_docid,
secured_attachmentid,
attachment_url;
// no docId
if (!(typeof docid === "string" && docid !== "")) {
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Cannot create document which id is undefined",
"reason": "Document id is undefined"
});
return;
}
// no cross domain ajax
if (priv.checkCors === false) {
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Browser does not support cross domain ajax requests",
"reason": "cors is undefined"
});
return;
}
secured_docid = priv.secureDocId(docid);
url = priv.url + '/' + priv.underscoreFileExtenisons(secured_docid);
// see if the underlying document exists
$.ajax({
url: url + '?_=' + Date.now(),
type: 'GET',
async: true,
dataType: 'text',
crossdomain : true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
success: function (response) {
doc = JSON.parse(response);
// the document exists - update document
doc._attachments = doc._attachments || {};
doc._attachments[command.getAttachmentId()] = {
"content_type": command.getAttachmentMimeType(),
"digest": "md5-" + command.md5SumAttachmentData(),
"length": command.getAttachmentLength()
};
// put updated document data
$.ajax({
url: url + '?_=' + Date.now(),
type: 'PUT',
data: JSON.stringify(doc),
async: true,
crossdomain: true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
// xhrFields: {withCredentials: 'true'},
success: function () {
secured_attachmentid = priv.secureDocId(command.getAttachmentId());
attachment_url = url + '.' +
priv.underscoreFileExtenisons(secured_attachmentid);
$.ajax({
url: attachment_url + '?_=' + Date.now(),
type: 'PUT',
data: JSON.stringify(command.getDoc()),
async: true,
crossdomain: true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
// xhrFields: {withCredentials: 'true'},
success: function () {
that.success({
ok: true,
id: command.getDocId() + '/' + command.getAttachmentId()
});
},
error: function () {
that.error({
"status": 409,
"statusText": "Conflicts",
"error": "conflicts",
"message": "Cannot modify document",
"reason": "Error trying to save attachment to remote storage"
});
return;
}
});
},
error: function () {
that.error({
"status": 409,
"statusText": "Conflicts",
"error": "conflicts",
"message": "Cannot modify document",
"reason": "Error trying to write to remote storage"
});
return;
}
});
},
error: function () {
// the document does not exist
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Impossible to add attachment",
"reason": "Document not found"
});
return;
}
});
};
/**
* Get a document or attachment from distant storage
* Options:
* - {boolean} revs Add simple revision history (false by default).
* - {boolean} revs_info Add revs info (false by default).
* - {boolean} conflicts Add conflict object (false by default).
* @method get
* @param {object} command The JIO command
*/
that.get = function (command) {
var docid = command.getDocId(),
doc,
url,
secured_docid,
secured_attachmentid,
attachment_url;
// no docId
if (!(typeof docid === "string" && docid !== "")) {
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Cannot create document which id is undefined",
"reason": "Document id is undefined"
});
return;
}
// no cors support
if (priv.checkCors === false) {
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Browser does not support cross domain ajax requests",
"reason": "cors is undefined"
});
return;
}
secured_docid = priv.secureDocId(command.getDocId());
url = priv.url + '/' + priv.underscoreFileExtenisons(secured_docid);
if (typeof command.getAttachmentId() === "string") {
secured_attachmentid = priv.secureDocId(command.getAttachmentId());
attachment_url = url + '.' + priv.underscoreFileExtenisons(
secured_attachmentid
);
// get attachment
$.ajax({
url: attachment_url + '?_=' + Date.now(),
type: 'GET',
async: true,
dataType: 'text',
crossdomain : true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
success: function (response) {
doc = JSON.parse(response);
that.success(doc);
},
error: function () {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the attachment",
"reason": "Attachment does not exist"
});
}
});
} else {
// get document
$.ajax({
url: url + '?_=' + Date.now(),
type: 'GET',
async: true,
dataType: 'text',
crossdomain : true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
success: function (response) {
// metadata_only should not be handled by jIO, as it is a
// webDav only option, shouldn't it?
// ditto for content_only
doc = JSON.parse(response);
that.success(doc);
},
error: function () {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the document",
"reason": "Document does not exist"
});
}
});
}
};
/**
* Remove a document or attachment
* @method remove
* @param {object} command The JIO command
*/
that.remove = function (command) {
var docid = command.getDocId(), doc, url,
secured_docid, secured_attachmentid, attachment_url,
attachment_list = [], i, j, k = 1, deleteAttachment;
// no docId
if (!(typeof docid === "string" && docid !== "")) {
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Cannot create document which id is undefined",
"reason": "Document id is undefined"
});
}
// no cors support
if (priv.checkCors === false) {
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Browser does not support cross domain ajax requests",
"reason": "cors is undefined"
});
}
secured_docid = priv.secureDocId(command.getDocId());
url = priv.url + '/' + priv.underscoreFileExtenisons(secured_docid);
// remove attachment
if (typeof command.getAttachmentId() === "string") {
secured_attachmentid = priv.secureDocId(command.getAttachmentId());
attachment_url = url + '.' + priv.underscoreFileExtenisons(
secured_attachmentid
);
$.ajax({
url: attachment_url + '?_=' + Date.now(),
type: 'DELETE',
async: true,
crossdomain : true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
success: function () {
// retrieve underlying document
$.ajax({
url: url + '?_=' + Date.now(),
type: 'GET',
async: true,
dataType: 'text',
crossdomain : true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
success: function (response) {
// underlying document
doc = JSON.parse(response);
// update doc._attachments
if (typeof doc._attachments === "object") {
if (typeof doc._attachments[command.getAttachmentId()] ===
"object") {
delete doc._attachments[command.getAttachmentId()];
if (priv.objectIsEmpty(doc._attachments)) {
delete doc._attachments;
}
// PUT back to server
$.ajax({
url: url + '?_=' + Date.now(),
type: 'PUT',
data: JSON.stringify(doc),
async: true,
crossdomain: true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
// xhrFields: {withCredentials: 'true'},
success: function () {
that.success({
"ok": true,
"id": command.getDocId() + '/' +
command.getAttachmentId()
});
},
error: function () {
that.error({
"status": 409,
"statusText": "Conflicts",
"error": "conflicts",
"message": "Cannot modify document",
"reason": "Error trying to update document attachments"
});
}
});
} else {
// sure this if-else is needed?
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the document",
"reason": "Error trying to update document attachments"
});
}
} else {
// no attachments, we are done
that.success({
"ok": true,
"id": command.getDocId() + '/' + command.getAttachmentId()
});
}
},
error: function () {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the document",
"reason": "Document does not exist"
});
}
});
},
error: function () {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the attachment",
"reason": "Error trying to remove attachment"
});
}
});
// remove document
} else {
// get document to also remove all attachments
$.ajax({
url: url + '?_=' + Date.now(),
type: 'GET',
async: true,
dataType: 'text',
crossdomain : true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
success: function (response) {
var x;
doc = JSON.parse(response);
// prepare attachment loop
if (typeof doc._attachments === "object") {
// prepare list of attachments
for (x in doc._attachments) {
if (doc._attachments.hasOwnProperty(x)) {
attachment_list.push(x);
}
}
}
// delete document
$.ajax({
url: url + '?_=' + Date.now(),
type: 'DELETE',
async: true,
crossdomain : true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
success: function () {
j = attachment_list.length;
// no attachments, done
if (j === 0) {
that.success({
"ok": true,
"id": command.getDocId()
});
} else {
deleteAttachment = function (attachment_url, j, k) {
$.ajax({
url: attachment_url + '?_=' + Date.now(),
type: 'DELETE',
async: true,
crossdomain : true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
success: function () {
// all deleted, return response, need k as async couter
if (j === k) {
that.success({
"ok": true,
"id": command.getDocId()
});
} else {
k += 1;
}
},
error: function () {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the attachment",
"reason": "Error trying to remove attachment"
});
}
});
};
for (i = 0; i < j; i += 1) {
secured_attachmentid = priv.secureDocId(attachment_list[i]);
attachment_url = url + '.' + priv.underscoreFileExtenisons(
secured_attachmentid
);
deleteAttachment(attachment_url, j, k);
}
}
},
error: function () {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the document",
"reason": "Error trying to remove document"
});
}
});
},
error: function () {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the document",
"reason": "Document does not exist"
});
}
});
}
};
/**
* Gets a document list from a distant dav storage
* Options:
* - {boolean} include_docs Also retrieve the actual document content.
* @method allDocs
* @param {object} command The JIO command
*/
//{
// "total_rows": 4,
// "rows": [
// {
// "id": "otherdoc",
// "key": "otherdoc",
// "value": {
// "rev": "1-3753476B70A49EA4D8C9039E7B04254C"
// }
// },{...}
// ]
//}
that.allDocs = function (command) {
var rows = [], url,
am = priv.newAsyncModule(),
o = {};
o.getContent = function (file) {
var docid = priv.secureDocId(file.id),
url = priv.url + '/' + docid;
$.ajax({
url: url + '?_=' + Date.now(),
type: 'GET',
async: true,
dataType: 'text',
headers: {
'Authorization': 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
success: function (content) {
file.doc = JSON.parse(content);
rows.push(file);
am.call(o, 'success');
},
error: function (type) {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the document",
"reason": "Cannot get a document from DAVStorage"
});
am.call(o, 'error', [type]);
}
});
};
o.getDocumentList = function () {
url = priv.url + '/';
$.ajax({
url: url + '?_=' + Date.now(),
type: 'PROPFIND',
async: true,
dataType: "xml",
crossdomain : true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
),
Depth: '1'
},
success: function (xml) {
var response = $(xml).find('D\\:response, response'),
len = response.length;
if (len === 1) {
return am.call(o, 'success');
}
am.wait(o, 'success', len - 2);
response.each(function (i, data) {
if (i > 0) { // exclude parent folder
var file = {
value: {}
};
$(data).find('D\\:href, href').each(function () {
var split = $(this).text().split('/');
file.id = split[split.length - 1];
file.id = priv.restoreSlashes(file.id);
file.key = file.id;
});
if (command.getOption('include_docs')) {
am.call(o, 'getContent', [file]);
} else {
rows.push(file);
am.call(o, 'success');
}
}
});
},
error: function (type) {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the document",
"reason": "Cannot get a document list from DAVStorage"
});
am.call(o, 'retry', [type]);
}
});
};
o.retry = function (error) {
am.neverCall(o, 'retry');
am.neverCall(o, 'success');
am.neverCall(o, 'error');
that.retry(error);
};
o.error = function (error) {
am.neverCall(o, 'retry');
am.neverCall(o, 'success');
am.neverCall(o, 'error');
that.error(error);
};
o.success = function () {
am.neverCall(o, 'retry');
am.neverCall(o, 'success');
am.neverCall(o, 'error');
that.success({
total_rows: rows.length,
rows: rows
});
};
// first get the XML list
am.call(o, 'getDocumentList');
};
return that;
});
\ No newline at end of file
/*
* Copyright 2013, Nexedi SA
* Released under the LGPL license.
* http://www.gnu.org/licenses/lgpl.html
*/
(function (scope, hex_md5) { (function (scope, hex_md5) {
"use strict"; "use strict";
var localstorage; var localstorage;
...@@ -262,6 +268,18 @@ var storage = function (spec, my) { ...@@ -262,6 +268,18 @@ var storage = function (spec, my) {
}); });
}; };
that.check = function (command) {
setTimeout(function () {
that.success({"ok": true, "id": command.getDocId()});
});
};
that.repair = function (command) {
setTimeout(function () {
that.success({"ok": true, "id": command.getDocId()});
});
};
that.success = function () {}; that.success = function () {};
that.retry = function () {}; that.retry = function () {};
that.error = function () {}; that.error = function () {};
...@@ -278,16 +296,10 @@ var storage = function (spec, my) { ...@@ -278,16 +296,10 @@ var storage = function (spec, my) {
that.addJob = function (method, storage_spec, doc, option, success, error) { that.addJob = function (method, storage_spec, doc, option, success, error) {
var command_opt = { var command_opt = {
doc: doc,
options: option, options: option,
callbacks: {success: success, error: error} callbacks: {success: success, error: error}
}; };
if (doc) {
if (method === 'get') {
command_opt.docid = doc;
} else {
command_opt.doc = doc;
}
}
jobManager.addJob(job({ jobManager.addJob(job({
storage: priv.storage(storage_spec || {}), storage: priv.storage(storage_spec || {}),
command: priv.newCommand(method, command_opt) command: priv.newCommand(method, command_opt)
...@@ -323,10 +335,49 @@ var allDocsCommand = function (spec, my) { ...@@ -323,10 +335,49 @@ var allDocsCommand = function (spec, my) {
return that; return that;
}; };
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */
var checkCommand = function (spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Methods //
that.getLabel = function () {
return 'check';
};
that.validateState = function () {
if (!(typeof that.getDocId() === "string" && that.getDocId() !==
"")) {
that.error({
"status": 20,
"statusText": "Document Id Required",
"error": "document_id_required",
"message": "The document id is not provided",
"reason": "Document id is undefined"
});
return false;
}
return true;
};
that.executeOn = function (storage) {
storage.check(that);
};
that.canBeRestored = function () {
return false;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */ /*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global postCommand: true, putCommand: true, getCommand: true, /*global postCommand: true, putCommand: true, getCommand: true,
removeCommand: true, allDocsCommand: true, removeCommand: true, allDocsCommand: true,
getAttachmentCommand: true, removeAttachmentCommand: true,
putAttachmentCommand: true, failStatus: true, doneStatus: true, putAttachmentCommand: true, failStatus: true, doneStatus: true,
checkCommand: true, repairCommand: true,
hex_md5: true */ hex_md5: true */
var command = function (spec, my) { var command = function (spec, my) {
var that = {}, var that = {},
...@@ -341,7 +392,11 @@ var command = function (spec, my) { ...@@ -341,7 +392,11 @@ var command = function (spec, my) {
'get': getCommand, 'get': getCommand,
'remove': removeCommand, 'remove': removeCommand,
'allDocs': allDocsCommand, 'allDocs': allDocsCommand,
'putAttachment': putAttachmentCommand 'getAttachment': getAttachmentCommand,
'putAttachment': putAttachmentCommand,
'removeAttachment': removeAttachmentCommand,
'check': checkCommand,
'repair': repairCommand
}; };
// creates the good command thanks to his label // creates the good command thanks to his label
if (spec.label && priv.commandlist[spec.label]) { if (spec.label && priv.commandlist[spec.label]) {
...@@ -357,11 +412,10 @@ var command = function (spec, my) { ...@@ -357,11 +412,10 @@ var command = function (spec, my) {
"_id": priv.doc.toString() "_id": priv.doc.toString()
}; };
} }
priv.docid = spec.docid || priv.doc._id;
priv.option = spec.options || {}; priv.option = spec.options || {};
priv.callbacks = spec.callbacks || {}; priv.callbacks = spec.callbacks || {};
priv.success = priv.callbacks.success || function () {}; priv.success = [priv.callbacks.success || function () {}];
priv.error = priv.callbacks.error || function () {}; priv.error = [priv.callbacks.error || function () {}];
priv.retry = function () { priv.retry = function () {
that.error({ that.error({
status: 13, status: 13,
...@@ -404,10 +458,7 @@ var command = function (spec, my) { ...@@ -404,10 +458,7 @@ var command = function (spec, my) {
* @return {string} The document id * @return {string} The document id
*/ */
that.getDocId = function () { that.getDocId = function () {
if (typeof priv.docid !== "string") { return priv.doc._id;
return undefined;
}
return priv.docid.split('/')[0];
}; };
/** /**
...@@ -416,19 +467,7 @@ var command = function (spec, my) { ...@@ -416,19 +467,7 @@ var command = function (spec, my) {
* @return {string} The attachment id * @return {string} The attachment id
*/ */
that.getAttachmentId = function () { that.getAttachmentId = function () {
if (typeof priv.docid !== "string") { return priv.doc._attachment;
return undefined;
}
return priv.docid.split('/')[1];
};
/**
* Returns the label of the command.
* @method getDoc
* @return {object} The document.
*/
that.getDoc = function () {
return priv.doc;
}; };
/** /**
...@@ -492,14 +531,13 @@ var command = function (spec, my) { ...@@ -492,14 +531,13 @@ var command = function (spec, my) {
* @param {object} storage The storage. * @param {object} storage The storage.
*/ */
that.validate = function (storage) { that.validate = function (storage) {
if (typeof priv.docid === "string" && if (typeof priv.doc._id === "string" && priv.doc._id.match(" ")) {
!priv.docid.match("^[^\/]+([\/][^\/]+)?$")) {
that.error({ that.error({
status: 21, "status": 21,
statusText: 'Invalid Document Id', "statusText": "Invalid Document Id",
error: 'invalid_document_id', "error": "invalid_document_id",
message: 'The document id must be like "abc" or "abc/def".', "message": "The document id is invalid",
reason: 'The document id is no like "abc" or "abc/def"' "reason": "The document id contains spaces"
}); });
return false; return false;
} }
...@@ -557,9 +595,14 @@ var command = function (spec, my) { ...@@ -557,9 +595,14 @@ var command = function (spec, my) {
*/ */
that.executeOn = function (storage) {}; that.executeOn = function (storage) {};
that.success = function (return_value) { that.success = function (return_value) {
var i;
priv.on_going = false; priv.on_going = false;
priv.success(return_value); for (i = 0; i < priv.success.length; i += 1) {
priv.success[i](return_value);
}
priv.end(doneStatus()); priv.end(doneStatus());
priv.success = [];
priv.error = [];
}; };
that.retry = function (return_error) { that.retry = function (return_error) {
priv.on_going = false; priv.on_going = false;
...@@ -570,13 +613,31 @@ var command = function (spec, my) { ...@@ -570,13 +613,31 @@ var command = function (spec, my) {
} }
}; };
that.error = function (return_error) { that.error = function (return_error) {
var i;
priv.on_going = false; priv.on_going = false;
priv.error(return_error); for (i = 0; i < priv.error.length; i += 1) {
priv.error[i](return_error);
}
priv.end(failStatus()); priv.end(failStatus());
priv.success = [];
priv.error = [];
}; };
that.end = function () { that.end = function () {
priv.end(doneStatus()); priv.end(doneStatus());
}; };
that.addCallbacks = function (success, error) {
if (arguments.length > 1) {
priv.success.push(success || function () {});
priv.error.push(error || function () {});
} else {
priv.success.push(function (response) {
(success || function () {})(undefined, response);
});
priv.error.push(function (err) {
(success || function () {})(err, undefined);
});
}
};
that.onSuccessDo = function (fun) { that.onSuccessDo = function (fun) {
if (fun) { if (fun) {
priv.success = fun; priv.success = fun;
...@@ -633,20 +694,23 @@ var command = function (spec, my) { ...@@ -633,20 +694,23 @@ var command = function (spec, my) {
}; };
/*jslint indent: 2, maxlen: 80, sloppy: true */ /*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */ /*global command: true */
var getCommand = function (spec, my) { var getAttachmentCommand = function (spec, my) {
var that = command(spec, my); var that = command(spec, my);
spec = spec || {}; spec = spec || {};
my = my || {}; my = my || {};
// Attributes // // Attributes //
// Methods // // Methods //
that.getLabel = function () { that.getLabel = function () {
return 'get'; return 'getAttachment';
};
that.executeOn = function (storage) {
storage.getAttachment(that);
}; };
that.validateState = function () { that.validateState = function () {
if (!(typeof that.getDocId() === "string" && if (!(typeof that.getDocId() === "string" && that.getDocId() !==
that.getDocId() !== "")) { "")) {
that.error({ that.error({
"status": 20, "status": 20,
"statusText": "Document Id Required", "statusText": "Document Id Required",
...@@ -656,7 +720,16 @@ var getCommand = function (spec, my) { ...@@ -656,7 +720,16 @@ var getCommand = function (spec, my) {
}); });
return false; return false;
} }
if (typeof that.getAttachmentId() === "string") { if (typeof that.getAttachmentId() !== "string") {
that.error({
"status": 22,
"statusText": "Attachment Id Required",
"error": "attachment_id_required",
"message": "The attachment id must be set",
"reason": "Attachment id not set"
});
return false;
}
if (that.getAttachmentId() === "") { if (that.getAttachmentId() === "") {
that.error({ that.error({
"status": 23, "status": 23,
...@@ -665,8 +738,36 @@ var getCommand = function (spec, my) { ...@@ -665,8 +738,36 @@ var getCommand = function (spec, my) {
"message": "The attachment id must not be an empty string", "message": "The attachment id must not be an empty string",
"reason": "Attachment id is empty" "reason": "Attachment id is empty"
}); });
return false;
} }
return true;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */
var getCommand = function (spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return 'get';
};
that.validateState = function () {
if (!(typeof that.getDocId() === "string" &&
that.getDocId() !== "")) {
that.error({
"status": 20,
"statusText": "Document Id Required",
"error": "document_id_required",
"message": "The document id is not provided",
"reason": "Document id is undefined"
});
return false;
} }
return true; return true;
}; };
...@@ -695,17 +796,6 @@ var postCommand = function (spec, my) { ...@@ -695,17 +796,6 @@ var postCommand = function (spec, my) {
}; };
that.validateState = function () { that.validateState = function () {
if (that.getAttachmentId() !== undefined) {
that.error({
"status": 21,
"statusText": "Invalid Document Id",
"error": "invalid_document_id",
"message": "The document id contains '/' characters " +
"which are forbidden",
"reason": "Document id contains '/' character(s)"
});
return false;
}
return true; return true;
}; };
that.executeOn = function (storage) { that.executeOn = function (storage) {
...@@ -789,17 +879,6 @@ var putCommand = function (spec, my) { ...@@ -789,17 +879,6 @@ var putCommand = function (spec, my) {
}); });
return false; return false;
} }
if (that.getAttachmentId() !== undefined) {
that.error({
"status": 21,
"statusText": "Invalid Document Id",
"error": "invalid_document_id",
"message": "The document id contains '/' characters " +
"which are forbidden",
"reason": "Document id contains '/' character(s)"
});
return false;
}
return true; return true;
}; };
that.executeOn = function (storage) { that.executeOn = function (storage) {
...@@ -809,14 +888,18 @@ var putCommand = function (spec, my) { ...@@ -809,14 +888,18 @@ var putCommand = function (spec, my) {
}; };
/*jslint indent: 2, maxlen: 80, sloppy: true */ /*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */ /*global command: true */
var removeCommand = function (spec, my) { var removeAttachmentCommand = function (spec, my) {
var that = command(spec, my); var that = command(spec, my);
spec = spec || {}; spec = spec || {};
my = my || {}; my = my || {};
// Attributes // // Attributes //
// Methods // // Methods //
that.getLabel = function () { that.getLabel = function () {
return 'remove'; return 'removeAttachment';
};
that.executeOn = function (storage) {
storage.removeAttachment(that);
}; };
that.validateState = function () { that.validateState = function () {
...@@ -831,7 +914,16 @@ var removeCommand = function (spec, my) { ...@@ -831,7 +914,16 @@ var removeCommand = function (spec, my) {
}); });
return false; return false;
} }
if (typeof that.getAttachmentId() === "string") { if (typeof that.getAttachmentId() !== "string") {
that.error({
"status": 22,
"statusText": "Attachment Id Required",
"error": "attachment_id_required",
"message": "The attachment id must be set",
"reason": "Attachment id not set"
});
return false;
}
if (that.getAttachmentId() === "") { if (that.getAttachmentId() === "") {
that.error({ that.error({
"status": 23, "status": 23,
...@@ -840,8 +932,35 @@ var removeCommand = function (spec, my) { ...@@ -840,8 +932,35 @@ var removeCommand = function (spec, my) {
"message": "The attachment id must not be an empty string", "message": "The attachment id must not be an empty string",
"reason": "Attachment id is empty" "reason": "Attachment id is empty"
}); });
return false;
} }
return true;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */
var removeCommand = function (spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return 'remove';
};
that.validateState = function () {
if (!(typeof that.getDocId() === "string" && that.getDocId() !==
"")) {
that.error({
"status": 20,
"statusText": "Document Id Required",
"error": "document_id_required",
"message": "The document id is not provided",
"reason": "Document id is undefined"
});
return false;
} }
return true; return true;
}; };
...@@ -853,6 +972,37 @@ var removeCommand = function (spec, my) { ...@@ -853,6 +972,37 @@ var removeCommand = function (spec, my) {
return that; return that;
}; };
/*jslint indent: 2, maxlen: 80, sloppy: true */ /*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */
var repairCommand = function (spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Methods //
that.getLabel = function () {
return 'repair';
};
that.validateState = function () {
if (!(typeof that.getDocId() === "string" && that.getDocId() !==
"")) {
that.error({
"status": 20,
"statusText": "Document Id Required",
"error": "document_id_required",
"message": "The document id is not provided",
"reason": "Document id is undefined"
});
return false;
}
return true;
};
that.executeOn = function (storage) {
storage.repair(that);
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global jobStatus: true */ /*global jobStatus: true */
var doneStatus = function (spec, my) { var doneStatus = function (spec, my) {
var that = jobStatus(spec, my); var that = jobStatus(spec, my);
...@@ -1230,16 +1380,9 @@ var job = function (spec) { ...@@ -1230,16 +1380,9 @@ var job = function (spec) {
* @param {object} job The other job. * @param {object} job The other job.
*/ */
that.update = function (job) { that.update = function (job) {
priv.command.error({ priv.command.addCallbacks(job.getCommand().onSuccessDo()[0],
status: 12, job.getCommand().onErrorDo()[0]);
statusText: 'Replaced',
error: 'replaced',
message: 'Job has been replaced by another one.',
reason: 'job has been replaced by another one'
});
priv.date = new Date(job.getDate().getTime()); priv.date = new Date(job.getDate().getTime());
priv.command = job.getCommand();
priv.status = job.getStatus();
}; };
/** /**
...@@ -1783,7 +1926,7 @@ var jobRules = (function () { ...@@ -1783,7 +1926,7 @@ var jobRules = (function () {
return 'wait'; return 'wait';
} }
}); });
Object.defineProperty(that, "none", { Object.defineProperty(that, "ok", {
configurable: false, configurable: false,
enumerable: false, enumerable: false,
writable: false, writable: false,
...@@ -1791,17 +1934,66 @@ var jobRules = (function () { ...@@ -1791,17 +1934,66 @@ var jobRules = (function () {
return 'none'; return 'none';
} }
}); });
that.default_action = that.none; that.default_action = that.ok;
that.default_compare = function (job1, job2) { that.default_compare = function (job1, job2) {
return (job1.getCommand().getDocId() === job2.getCommand().getDocId() && return job1.getId() !== job2.getId() &&
job1.getCommand().getDocInfo('_rev') === job1.getStatus().getLabel() !== "done" &&
job2.getCommand().getDocInfo('_rev') && job1.getStatus().getLabel() !== "fail" &&
job1.getCommand().getOption('rev') ===
job2.getCommand().getOption('rev') &&
JSON.stringify(job1.getStorage().serialized()) === JSON.stringify(job1.getStorage().serialized()) ===
JSON.stringify(job2.getStorage().serialized())); JSON.stringify(job2.getStorage().serialized());
}; };
// Compare Functions //
Object.defineProperty(that, "sameDocumentId", {
configurable: false,
enumerable: false,
writable: false,
value: function (job1, job2) {
return job1.getCommand().getDocId() === job2.getCommand().getDocId();
}
});
Object.defineProperty(that, "sameRevision", {
configurable: false,
enumerable: false,
writable: false,
value: function (job1, job2) {
return job1.getCommand().getDocInfo("_rev") ===
job2.getCommand().getDocInfo("_rev");
}
});
Object.defineProperty(that, "sameAttachmentId", {
configurable: false,
enumerable: false,
writable: false,
value: function (job1, job2) {
return job1.getCommand().getAttachmentId() ===
job2.getCommand().getAttachmentId();
}
});
Object.defineProperty(that, "sameDocument", {
configurable: false,
enumerable: false,
writable: false,
value: function (job1, job2) {
return JSON.stringify(job1.getCommand().cloneDoc()) ===
JSON.stringify(job2.getCommand().cloneDoc());
}
});
Object.defineProperty(that, "sameOption", {
configurable: false,
enumerable: false,
writable: false,
value: function (job1, job2) {
return JSON.stringify(job1.getCommand().cloneOption()) ===
JSON.stringify(job2.getCommand().cloneOption());
}
});
// Methods // // Methods //
/** /**
* Returns an action according the jobs given in parameters. * Returns an action according the jobs given in parameters.
...@@ -1811,16 +2003,28 @@ var jobRules = (function () { ...@@ -1811,16 +2003,28 @@ var jobRules = (function () {
* @return {string} An action string. * @return {string} An action string.
*/ */
priv.getAction = function (job1, job2) { priv.getAction = function (job1, job2) {
var j1label, j2label, j1status; var method1, method2, tmp = priv.action, i, j, condition_list = [], res;
j1label = job1.getCommand().getLabel(); method1 = job1.getCommand().getLabel();
j2label = job2.getCommand().getLabel(); method2 = job2.getCommand().getLabel();
j1status = (job1.getStatus().getLabel() === 'on going' ? tmp = tmp[method1] = tmp[method1] || {};
'on going' : 'not on going'); tmp = tmp[method2] = tmp[method2] || [];
if (priv.action[j1label] && priv.action[j1label][j1status] && for (i = 0; i < tmp.length; i += 1) {
priv.action[j1label][j1status][j2label]) { // browsing all method1 method2 rules
return priv.action[j1label][j1status][j2label](job1, job2); condition_list = tmp[i].condition_list;
res = true;
for (j = 0; j < condition_list.length; j += 1) {
// test all the rule's conditions
if (!condition_list[j](job1, job2)) {
res = false;
break;
}
}
if (res) {
// if all respects condition list, then action
return tmp[i].rule();
} }
return that.default_action(job1, job2); }
return that.default_action();
}; };
/** /**
...@@ -1831,10 +2035,11 @@ var jobRules = (function () { ...@@ -1831,10 +2035,11 @@ var jobRules = (function () {
* @return {boolean} true if comparable, else false. * @return {boolean} true if comparable, else false.
*/ */
priv.canCompare = function (job1, job2) { priv.canCompare = function (job1, job2) {
var job1label = job1.getCommand().getLabel(), var method1, method2;
job2label = job2.getCommand().getLabel(); method1 = job1.getCommand().getLabel();
if (priv.compare[job1label] && priv.compare[job2label]) { method2 = job2.getCommand().getLabel();
return priv.compare[job1label][job2label](job1, job2); if (priv.compare[method1] && priv.compare[method1][method2]) {
return priv.compare[method1][method2](job1, job2);
} }
return that.default_compare(job1, job2); return that.default_compare(job1, job2);
}; };
...@@ -1876,11 +2081,14 @@ var jobRules = (function () { ...@@ -1876,11 +2081,14 @@ var jobRules = (function () {
configurable: false, configurable: false,
enumerable: false, enumerable: false,
writable: false, writable: false,
value: function (method1, ongoing, method2, rule) { value: function (method1, method2, condition_list, rule) {
var ongoing_s = (ongoing ? 'on going' : 'not on going'); var tmp = priv.action;
priv.action[method1] = priv.action[method1] || {}; tmp = tmp[method1] = tmp[method1] || {};
priv.action[method1][ongoing_s] = priv.action[method1][ongoing_s] || {}; tmp = tmp[method2] = tmp[method2] || [];
priv.action[method1][ongoing_s][method2] = rule; tmp.push({
"condition_list": condition_list,
"rule": rule
});
} }
}); });
...@@ -1904,112 +2112,125 @@ var jobRules = (function () { ...@@ -1904,112 +2112,125 @@ var jobRules = (function () {
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// Adding some rules // Adding some rules
/* /*
LEGEND: Rules
- s: storage original job |job to add |condition |action
- m: method
- n: name post post same doc update
- c: content " " same docid, same rev wait
- o: options " put " "
- =: are equal " putA " "
- !: are not equal " remove " "
" removeA " "
select ALL s= n= put post same docid, same rev wait
removefailordone fail|done " put same doc update
/ elim repl nacc wait " " same docid, same rev wait
Remove !ongoing Save 1 x x x " putA " "
Save !ongoing Remove 1 x x x " remove " "
GetList !ongoing GetList 0 1 x x " removeA " "
Remove !ongoing Remove 0 1 x x putA post same docid, same rev wait
Load !ongoing Load 0 1 x x " put " "
Save c= !ongoing Save 0 1 x x " putA same doc update
Save c! !ongoing Save 0 1 x x " " same docid, same rev, same attmt wait
GetList ongoing GetList 0 0 1 x " remove same docid, same rev "
Remove ongoing Remove 0 0 1 x " removeA same docid, same rev, same attmt "
Remove ongoing Load 0 0 1 x remove post same docid, same rev wait
Remove !ongoing Load 0 0 1 x " put " "
Load ongoing Load 0 0 1 x " putA " "
Save c= ongoing Save 0 0 1 x " remove " update
Remove ongoing Save 0 0 0 1 " removeA " wait
Load ongoing Remove 0 0 0 1 removeA post same docid, same rev wait
Load ongoing Save 0 0 0 1 " put " "
Load !ongoing Remove 0 0 0 1 " putA same docid, same rev, same attmt "
Load !ongoing Save 0 0 0 1 " remove same docid, same rev "
Save ongoing Remove 0 0 0 1 " removeA same doc update
Save ongoing Load 0 0 0 1 " removeA same docid, same rev, same attmt wait
Save c! ongoing Save 0 0 0 1 get get same doc, same options update
Save !ongoing Load 0 0 0 1 getA getA same doc, same options update
GetList ongoing Remove 0 0 0 0 allDocs allDocs same doc, same options update
GetList ongoing Load 0 0 0 0 */
GetList ongoing Save 0 0 0 0
GetList !ongoing Remove 0 0 0 0 that.addActionRule("post", "post", [that.sameDocument], that.update);
GetList !ongoing Load 0 0 0 0 that.addActionRule("post", "post",
GetList !ongoing Save 0 0 0 0 [that.sameDocumentId, that.sameRevision], that.wait);
Remove ongoing GetList 0 0 0 0 that.addActionRule("post", "put",
Remove !ongoing GetList 0 0 0 0 [that.sameDocumentId, that.sameRevision], that.wait);
Load ongoing GetList 0 0 0 0 that.addActionRule("post", "putAttachment",
Load !ongoing GetList 0 0 0 0 [that.sameDocumentId, that.sameRevision], that.wait);
Save ongoing GetList 0 0 0 0 that.addActionRule("post", "remove",
Save !ongoing GetList 0 0 0 0 [that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule("post", "removeAttachment",
For more information, see documentation [that.sameDocumentId, that.sameRevision], that.wait);
*/
that.addActionRule('post', true, 'post', that.dontAccept); that.addActionRule("put", "post",
that.addActionRule('post', true, 'put', that.wait); [that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule('post', true, 'get', that.wait); that.addActionRule("put", "put", [that.sameDocument], that.update);
that.addActionRule('post', true, 'remove', that.wait); that.addActionRule("put", "put",
that.addActionRule('post', true, 'putAttachment', that.wait); [that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule('post', false, 'post', that.update); that.addActionRule("put", "putAttachment",
that.addActionRule('post', false, 'put', that.wait); [that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule('post', false, 'get', that.wait); that.addActionRule("put", "remove",
that.addActionRule('post', false, 'remove', that.eliminate); [that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule('post', false, 'putAttachment', that.wait); that.addActionRule("put", "removeAttachment",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule('put', true, 'post', that.dontAccept);
that.addActionRule('put', true, 'put', that.wait); that.addActionRule("putAttachment", "post",
that.addActionRule('put', true, 'get', that.wait); [that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule('put', true, 'remove', that.wait); that.addActionRule("putAttachment", "put",
that.addActionRule('put', true, 'putAttachment', that.wait); [that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule('put', false, 'post', that.dontAccept); that.addActionRule("putAttachment", "putAttachment", [that.sameDocument],
that.addActionRule('put', false, 'put', that.update); that.update);
that.addActionRule('put', false, 'get', that.wait); that.addActionRule("putAttachment", "putAttachment", [
that.addActionRule('put', false, 'remove', that.eliminate); that.sameDocumentId,
that.addActionRule('put', false, 'putAttachment', that.wait); that.sameRevision,
that.sameAttachmentId
that.addActionRule('get', true, 'post', that.wait); ], that.wait);
that.addActionRule('get', true, 'put', that.wait); that.addActionRule("putAttachment", "remove",
that.addActionRule('get', true, 'get', that.dontAccept); [that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule('get', true, 'remove', that.wait); that.addActionRule("putAttachment", "removeAttachment", [
that.addActionRule('get', true, 'putAttachment', that.wait); that.sameDocumentId,
that.addActionRule('get', false, 'post', that.wait); that.sameRevision,
that.addActionRule('get', false, 'put', that.wait); that.sameAttachmentId
that.addActionRule('get', false, 'get', that.update); ], that.wait);
that.addActionRule('get', false, 'remove', that.wait);
that.addActionRule('get', false, 'putAttachment', that.wait); that.addActionRule("remove", "post",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule('remove', true, 'post', that.wait); that.addActionRule("remove", "put",
that.addActionRule('remove', true, 'get', that.dontAccept); [that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule('remove', true, 'remove', that.dontAccept); that.addActionRule("remove", "putAttachment",
that.addActionRule('remove', true, 'putAttachment', that.dontAccept); [that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule('remove', false, 'post', that.eliminate); that.addActionRule("remove", "remove",
that.addActionRule('remove', false, 'put', that.dontAccept); [that.sameDocumentId, that.sameRevision], that.update);
that.addActionRule('remove', false, 'get', that.dontAccept); that.addActionRule("remove", "removeAttachment",
that.addActionRule('remove', false, 'remove', that.update); [that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule('remove', false, 'putAttachment', that.dontAccept);
that.addActionRule("removeAttachment", "post",
that.addActionRule('allDocs', true, 'allDocs', that.dontAccept); [that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule('allDocs', false, 'allDocs', that.update); that.addActionRule("removeAttachment", "put",
[that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule('putAttachment', true, 'post', that.dontAccept); that.addActionRule("removeAttachment", "putAttachment", [
that.addActionRule('putAttachment', true, 'put', that.wait); that.sameDocumentId,
that.addActionRule('putAttachment', true, 'get', that.wait); that.sameRevision,
that.addActionRule('putAttachment', true, 'remove', that.wait); that.sameAttachmentId
that.addActionRule('putAttachment', true, 'putAttachment', that.wait); ], that.wait);
that.addActionRule('putAttachment', false, 'post', that.dontAccept); that.addActionRule("removeAttachment", "remove",
that.addActionRule('putAttachment', false, 'put', that.wait); [that.sameDocumentId, that.sameRevision], that.wait);
that.addActionRule('putAttachment', false, 'get', that.wait); that.addActionRule("removeAttachment", "removeAttachment",
that.addActionRule('putAttachment', false, 'remove', that.eliminate); [that.sameDocument], that.update);
that.addActionRule('putAttachment', false, 'putAttachment', that.update); that.addActionRule("removeAttachment", "removeAttachment", [
that.sameDocumentId,
that.sameRevision,
that.sameAttachmentId
], that.wait);
that.addActionRule("get", "get",
[that.sameDocument, that.sameOption], that.update);
that.addActionRule("getAttachment", "getAttachment",
[that.sameDocument, that.sameOption], that.update);
that.addActionRule("allDocs", "allDocs",
[that.sameDocument, that.sameOption], that.update);
// end adding rules // end adding rules
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
return that; return that;
...@@ -2020,7 +2241,8 @@ var jobRules = (function () { ...@@ -2020,7 +2241,8 @@ var jobRules = (function () {
storage_type_object: true, invalidStorageType: true, jobRules: true, storage_type_object: true, invalidStorageType: true, jobRules: true,
job: true, postCommand: true, putCommand: true, getCommand:true, job: true, postCommand: true, putCommand: true, getCommand:true,
allDocsCommand: true, putAttachmentCommand: true, allDocsCommand: true, putAttachmentCommand: true,
removeCommand: true */ getAttachmentCommand: true, removeAttachmentCommand: true,
removeCommand: true, checkCommand: true, repairCommand: true */
// Class jio // Class jio
var that = {}, priv = {}, jio_id_array_name = 'jio/id_array'; var that = {}, priv = {}, jio_id_array_name = 'jio/id_array';
spec = spec || {}; spec = spec || {};
...@@ -2229,12 +2451,13 @@ priv.addJob = function (commandCreator, spec) { ...@@ -2229,12 +2451,13 @@ priv.addJob = function (commandCreator, spec) {
* Post a document. * Post a document.
* @method post * @method post
* @param {object} doc The document object. Contains at least: * @param {object} doc The document object. Contains at least:
* - {string} _id The document id (optional), "/" are forbidden * - {string} _id The document id (optional)
* For revision managing: choose at most one of the following informations:
* - {string} _rev The revision we want to update
* - {string} _revs_info The revision information we want the document to have
* - {string} _revs The revision history we want the document to have
* @param {object} options (optional) Contains some options: * @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity. * - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} revs Include revision history of the document.
* - {boolean} revs_info Retreive the revisions.
* - {boolean} conflicts Retreive the conflict list.
* @param {function} callback (optional) The callback(err,response). * @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this * @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success", * callback is given in parameter, "callback" is changed as "success",
...@@ -2262,12 +2485,13 @@ Object.defineProperty(that, "post", { ...@@ -2262,12 +2485,13 @@ Object.defineProperty(that, "post", {
* Put a document. * Put a document.
* @method put * @method put
* @param {object} doc The document object. Contains at least: * @param {object} doc The document object. Contains at least:
* - {string} _id The document id, "/" are forbidden * - {string} _id The document id
* For revision managing: choose at most one of the following informations:
* - {string} _rev The revision we want to update
* - {string} _revs_info The revision information we want the document to have
* - {string} _revs The revision history we want the document to have
* @param {object} options (optional) Contains some options: * @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity. * - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} revs Include revision history of the document.
* - {boolean} revs_info Retreive the revisions.
* - {boolean} conflicts Retreive the conflict list.
* @param {function} callback (optional) The callback(err,response). * @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this * @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success", * callback is given in parameter, "callback" is changed as "success",
...@@ -2294,10 +2518,13 @@ Object.defineProperty(that, "put", { ...@@ -2294,10 +2518,13 @@ Object.defineProperty(that, "put", {
/** /**
* Get a document. * Get a document.
* @method get * @method get
* @param {string} docid The document id: "doc_id" or "doc_id/attachmt_id". * @param {string} doc The document object. Contains at least:
* - {string} _id The document id
* For revision managing:
* - {string} _rev The revision we want to get. (optional)
* @param {object} options (optional) Contains some options: * @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity. * - {number} max_retry The number max of retries, 0 = infinity.
* - {string} rev The revision we want to get. * For revision managing:
* - {boolean} revs Include revision history of the document. * - {boolean} revs Include revision history of the document.
* - {boolean} revs_info Include list of revisions, and their availability. * - {boolean} revs_info Include list of revisions, and their availability.
* - {boolean} conflicts Include a list of conflicts. * - {boolean} conflicts Include a list of conflicts.
...@@ -2310,14 +2537,14 @@ Object.defineProperty(that, "get", { ...@@ -2310,14 +2537,14 @@ Object.defineProperty(that, "get", {
configurable: false, configurable: false,
enumerable: false, enumerable: false,
writable: false, writable: false,
value: function (id, options, success, error) { value: function (doc, options, success, error) {
var param = priv.parametersToObject( var param = priv.parametersToObject(
[options, success, error], [options, success, error],
{max_retry: 3} {max_retry: 3}
); );
priv.addJob(getCommand, { priv.addJob(getCommand, {
docid: id, doc: doc,
options: param.options, options: param.options,
callbacks: {success: param.success, error: param.error} callbacks: {success: param.success, error: param.error}
}); });
...@@ -2328,12 +2555,11 @@ Object.defineProperty(that, "get", { ...@@ -2328,12 +2555,11 @@ Object.defineProperty(that, "get", {
* Remove a document. * Remove a document.
* @method remove * @method remove
* @param {object} doc The document object. Contains at least: * @param {object} doc The document object. Contains at least:
* - {string} _id The document id: "doc_id" or "doc_id/attachment_id" * - {string} _id The document id
* For revision managing:
* - {string} _rev The revision we want to remove
* @param {object} options (optional) Contains some options: * @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity. * - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} revs Include revision history of the document.
* - {boolean} revs_info Include list of revisions, and their availability.
* - {boolean} conflicts Include a list of conflicts.
* @param {function} callback (optional) The callback(err,response). * @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this * @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success", * callback is given in parameter, "callback" is changed as "success",
...@@ -2363,9 +2589,6 @@ Object.defineProperty(that, "remove", { ...@@ -2363,9 +2589,6 @@ Object.defineProperty(that, "remove", {
* @param {object} options (optional) Contains some options: * @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity. * - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} include_docs Include document metadata * - {boolean} include_docs Include document metadata
* - {boolean} revs Include revision history of the document.
* - {boolean} revs_info Include revisions.
* - {boolean} conflicts Include conflicts.
* @param {function} callback (optional) The callback(err,response). * @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this * @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success", * callback is given in parameter, "callback" is changed as "success",
...@@ -2388,19 +2611,51 @@ Object.defineProperty(that, "allDocs", { ...@@ -2388,19 +2611,51 @@ Object.defineProperty(that, "allDocs", {
} }
}); });
/**
* Get an attachment from a document.
* @method gettAttachment
* @param {object} doc The document object. Contains at least:
* - {string} _id The document id
* - {string} _attachment The attachment id
* For revision managing:
* - {string} _rev The document revision
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* @param {function} callback (optional) The callback(err,respons)
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "getAttachment", {
configurable: false,
enumerable: false,
writable: false,
value: function (doc, options, success, error) {
var param = priv.parametersToObject(
[options, success, error],
{max_retry: 3}
);
priv.addJob(getAttachmentCommand, {
doc: doc,
options: param.options,
callbacks: {success: param.success, error: param.error}
});
}
});
/** /**
* Put an attachment to a document. * Put an attachment to a document.
* @method putAttachment * @method putAttachment
* @param {object} doc The document object. Contains at least: * @param {object} doc The document object. Contains at least:
* - {string} id The document id: "doc_id/attchment_id" * - {string} _id The document id
* - {string} data Base64 attachment data * - {string} _attachment The attachment id
* - {string} mimetype The attachment mimetype * - {string} _data The attachment data
* - {string} rev The attachment revision * - {string} _mimetype The attachment mimetype
* For revision managing:
* - {string} _rev The document revision
* @param {object} options (optional) Contains some options: * @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity. * - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} revs Include revision history of the document.
* - {boolean} revs_info Include revisions.
* - {boolean} conflicts Include conflicts.
* @param {function} callback (optional) The callback(err,respons) * @param {function} callback (optional) The callback(err,respons)
* @param {function} error (optional) The callback on error, if this * @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success", * callback is given in parameter, "callback" is changed as "success",
...@@ -2411,19 +2666,106 @@ Object.defineProperty(that, "putAttachment", { ...@@ -2411,19 +2666,106 @@ Object.defineProperty(that, "putAttachment", {
enumerable: false, enumerable: false,
writable: false, writable: false,
value: function (doc, options, success, error) { value: function (doc, options, success, error) {
var param, k, doc_with_underscores = {}; var param = priv.parametersToObject(
param = priv.parametersToObject(
[options, success, error], [options, success, error],
{max_retry: 0} {max_retry: 0}
); );
for (k in doc) {
if (doc.hasOwnProperty(k) && k.match('[^_].*')) { priv.addJob(putAttachmentCommand, {
doc_with_underscores["_" + k] = doc[k]; doc: doc,
options: param.options,
callbacks: {success: param.success, error: param.error}
});
}
});
/**
* Put an attachment to a document.
* @method putAttachment
* @param {object} doc The document object. Contains at least:
* - {string} _id The document id
* - {string} _attachment The attachment id
* For revision managing:
* - {string} _rev The document revision
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* @param {function} callback (optional) The callback(err,respons)
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "removeAttachment", {
configurable: false,
enumerable: false,
writable: false,
value: function (doc, options, success, error) {
var param = priv.parametersToObject(
[options, success, error],
{max_retry: 0}
);
priv.addJob(removeAttachmentCommand, {
doc: doc,
options: param.options,
callbacks: {success: param.success, error: param.error}
});
} }
});
/**
* Check a document.
* @method check
* @param {object} doc The document object. Contains at least:
* - {string} _id The document id
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "check", {
configurable: false,
enumerable: false,
writable: false,
value: function (doc, options, success, callback) {
var param = priv.parametersToObject(
[options, success, callback],
{max_retry: 3}
);
priv.addJob(checkCommand, {
doc: doc,
options: param.options,
callbacks: {success: param.success, error: param.error}
});
} }
});
priv.addJob(putAttachmentCommand, { /**
doc: doc_with_underscores, * Repair a document.
* @method repair
* @param {object} doc The document object. Contains at least:
* - {string} _id The document id
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "repair", {
configurable: false,
enumerable: false,
writable: false,
value: function (doc, options, success, callback) {
var param = priv.parametersToObject(
[options, success, callback],
{max_retry: 3}
);
priv.addJob(repairCommand, {
doc: doc,
options: param.options, options: param.options,
callbacks: {success: param.success, error: param.error} callbacks: {success: param.success, error: param.error}
}); });
......
/*
* Copyright 2013, Nexedi SA
* Released under the LGPL license.
* http://www.gnu.org/licenses/lgpl.html
*/
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global jIO: true, localStorage: true, setTimeout: true,
complex_queries: true */
/**
* JIO Local Storage. Type = 'local'.
* Local browser "database" storage.
*
* Storage Description:
*
* {
* "type": "local",
* "username": <non empty string>, // to define user space
* "application_name": <string> // default 'untitled'
* }
*
* Document are stored in path
* 'jio/localstorage/username/application_name/document_id' like this:
*
* {
* "_id": "document_id",
* "_attachments": {
* "attachment_name": {
* "length": data_length,
* "digest": "md5-XXX",
* "content_type": "mime/type"
* },
* "attachment_name2": {..}, ...
* },
* "metadata_name": "metadata_value"
* "metadata_name2": ...
* ...
* }
*
* Only "_id" and "_attachments" are specific metadata keys, other one can be
* added without loss.
*
* @class LocalStorage
*/
jIO.addStorageType('local', function (spec, my) {
spec = spec || {};
var that, priv, localstorage;
that = my.basicStorage(spec, my);
priv = {};
/*
* Wrapper for the localStorage used to simplify instion of any kind of
* values
*/
localstorage = {
getItem: function (item) {
var value = localStorage.getItem(item);
return value === null ? null : JSON.parse(value);
},
setItem: function (item, value) {
return localStorage.setItem(item, JSON.stringify(value));
},
removeItem: function (item) {
return localStorage.removeItem(item);
}
};
// attributes
priv.username = spec.username || '';
priv.application_name = spec.application_name || 'untitled';
priv.localpath = 'jio/localstorage/' + priv.username + '/' +
priv.application_name;
// ==================== Tools ====================
/**
* Generate a new uuid
* @method generateUuid
* @return {string} The new uuid
*/
priv.generateUuid = function () {
var S4 = function () {
/* 65536 */
var i, string = Math.floor(
Math.random() * 0x10000
).toString(16);
for (i = string.length; i < 4; i += 1) {
string = '0' + string;
}
return string;
};
return S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() +
S4() + S4();
};
/**
* Checks if an object has no enumerable keys
* @method objectIsEmpty
* @param {object} obj The object
* @return {boolean} true if empty, else false
*/
priv.objectIsEmpty = function (obj) {
var k;
for (k in obj) {
if (obj.hasOwnProperty(k)) {
return false;
}
}
return true;
};
// ===================== overrides ======================
that.specToStore = function () {
return {
"application_name": priv.application_name,
"username": priv.username
};
};
that.validateState = function () {
if (typeof priv.username === "string" && priv.username !== '') {
return '';
}
return 'Need at least one parameter: "username".';
};
// ==================== commands ====================
/**
* Create a document in local storage.
* @method post
* @param {object} command The JIO command
*/
that.post = function (command) {
setTimeout(function () {
var doc, doc_id = command.getDocId();
if (!doc_id) {
doc_id = priv.generateUuid();
}
doc = localstorage.getItem(priv.localpath + "/" + doc_id);
if (doc === null) {
// the document does not exist
doc = command.cloneDoc();
doc._id = doc_id;
delete doc._attachments;
localstorage.setItem(priv.localpath + "/" + doc_id, doc);
that.success({
"ok": true,
"id": doc_id
});
} else {
// the document already exists
that.error({
"status": 409,
"statusText": "Conflicts",
"error": "conflicts",
"message": "Cannot create a new document",
"reason": "Document already exists"
});
}
});
};
/**
* Create or update a document in local storage.
* @method put
* @param {object} command The JIO command
*/
that.put = function (command) {
setTimeout(function () {
var doc, tmp;
doc = localstorage.getItem(priv.localpath + "/" + command.getDocId());
if (doc === null) {
// the document does not exist
doc = command.cloneDoc();
delete doc._attachments;
} else {
// the document already exists
tmp = command.cloneDoc();
tmp._attachments = doc._attachments;
doc = tmp;
}
// write
localstorage.setItem(priv.localpath + "/" + command.getDocId(), doc);
that.success({
"ok": true,
"id": command.getDocId()
});
});
};
/**
* Add an attachment to a document
* @method putAttachment
* @param {object} command The JIO command
*/
that.putAttachment = function (command) {
setTimeout(function () {
var doc;
doc = localstorage.getItem(priv.localpath + "/" + command.getDocId());
if (doc === null) {
// the document does not exist
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Impossible to add attachment",
"reason": "Document not found"
});
return;
}
// the document already exists
doc._attachments = doc._attachments || {};
doc._attachments[command.getAttachmentId()] = {
"content_type": command.getAttachmentMimeType(),
"digest": "md5-" + command.md5SumAttachmentData(),
"length": command.getAttachmentLength()
};
// upload data
localstorage.setItem(priv.localpath + "/" + command.getDocId() + "/" +
command.getAttachmentId(),
command.getAttachmentData());
// write document
localstorage.setItem(priv.localpath + "/" + command.getDocId(), doc);
that.success({
"ok": true,
"id": command.getDocId(),
"attachment": command.getAttachmentId()
});
});
};
/**
* Get a document
* @method get
* @param {object} command The JIO command
*/
that.get = function (command) {
setTimeout(function () {
var doc = localstorage.getItem(priv.localpath + "/" + command.getDocId());
if (doc !== null) {
that.success(doc);
} else {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the document",
"reason": "Document does not exist"
});
}
});
};
/**
* Get a attachment
* @method getAttachment
* @param {object} command The JIO command
*/
that.getAttachment = function (command) {
setTimeout(function () {
var doc = localstorage.getItem(priv.localpath + "/" + command.getDocId() +
"/" + command.getAttachmentId());
if (doc !== null) {
that.success(doc);
} else {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the attachment",
"reason": "Attachment does not exist"
});
}
});
};
/**
* Remove a document
* @method remove
* @param {object} command The JIO command
*/
that.remove = function (command) {
setTimeout(function () {
var doc, i, attachment_list;
doc = localstorage.getItem(priv.localpath + "/" + command.getDocId());
attachment_list = [];
if (doc !== null && typeof doc === "object") {
if (typeof doc._attachments === "object") {
// prepare list of attachments
for (i in doc._attachments) {
if (doc._attachments.hasOwnProperty(i)) {
attachment_list.push(i);
}
}
}
} else {
return that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Document not found",
"reason": "missing"
});
}
localstorage.removeItem(priv.localpath + "/" + command.getDocId());
// delete all attachments
for (i = 0; i < attachment_list.length; i += 1) {
localstorage.removeItem(priv.localpath + "/" + command.getDocId() +
"/" + attachment_list[i]);
}
that.success({
"ok": true,
"id": command.getDocId()
});
});
};
/**
* Remove an attachment
* @method removeAttachment
* @param {object} command The JIO command
*/
that.removeAttachment = function (command) {
setTimeout(function () {
var doc, error, i, attachment_list;
error = function (word) {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": word + " not found",
"reason": "missing"
});
};
doc = localstorage.getItem(priv.localpath + "/" + command.getDocId());
// remove attachment from document
if (doc !== null && typeof doc === "object" &&
typeof doc._attachments === "object") {
if (typeof doc._attachments[command.getAttachmentId()] ===
"object") {
delete doc._attachments[command.getAttachmentId()];
if (priv.objectIsEmpty(doc._attachments)) {
delete doc._attachments;
}
localstorage.setItem(priv.localpath + "/" + command.getDocId(),
doc);
localstorage.removeItem(priv.localpath + "/" + command.getDocId() +
"/" + command.getAttachmentId());
that.success({
"ok": true,
"id": command.getDocId(),
"attachment": command.getAttachmentId()
});
} else {
error("Attachment");
}
} else {
error("Document");
}
});
};
/**
* Get all filenames belonging to a user from the document index
* @method allDocs
* @param {object} command The JIO command
*/
that.allDocs = function (command) {
var i, row, path_re, rows = [], document_list = [], option, document_object;
path_re = new RegExp(
"^" + complex_queries.stringEscapeRegexpCharacters(priv.localpath) +
"/[^/]+$"
);
option = command.cloneOption();
if (typeof complex_queries !== "object" ||
(option.query === undefined && option.sort_on === undefined &&
option.select_list === undefined &&
option.include_docs === undefined)) {
rows = [];
for (i in localStorage) {
if (localStorage.hasOwnProperty(i)) {
// filter non-documents
if (path_re.test(i)) {
row = { value: {} };
row.id = i.split('/').slice(-1)[0];
row.key = row.id;
if (command.getOption('include_docs')) {
row.doc = JSON.parse(localStorage.getItem(i));
}
rows.push(row);
}
}
}
that.success({"rows": rows, "total_rows": rows.length});
} else {
// create complex query object from returned results
for (i in localStorage) {
if (localStorage.hasOwnProperty(i)) {
if (path_re.test(i)) {
document_list.push(localstorage.getItem(i));
}
}
}
option.select_list = option.select_list || [];
option.select_list.push("_id");
if (option.include_docs === true) {
document_object = {};
document_list.forEach(function (meta) {
document_object[meta._id] = meta;
});
}
complex_queries.QueryFactory.create(option.query || "").
exec(document_list, option);
document_list = document_list.map(function (value) {
var o = {
"id": value._id,
"key": value._id
};
if (option.include_docs === true) {
o.doc = document_object[value._id];
delete document_object[value._id];
}
delete value._id;
o.value = value;
return o;
});
that.success({"total_rows": document_list.length,
"rows": document_list});
}
};
return that;
});
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -13,12 +13,10 @@ ...@@ -13,12 +13,10 @@
gadget_scope_dict = {}, gadget_scope_dict = {},
javascript_registration_dict = {}, javascript_registration_dict = {},
stylesheet_registration_dict = {}, stylesheet_registration_dict = {},
root_gadget, gadget_loading_klass,
rootrenderJS, methods,
renderJS, loading_gadget_promise,
declareGadget, renderJS;
declareJavascript,
methods;
function RenderJSGadget() {} function RenderJSGadget() {}
RenderJSGadget.prototype.title = ""; RenderJSGadget.prototype.title = "";
...@@ -28,7 +26,12 @@ ...@@ -28,7 +26,12 @@
RenderJSGadget.prototype.required_css_list = []; RenderJSGadget.prototype.required_css_list = [];
RenderJSGadget.prototype.required_js_list = []; RenderJSGadget.prototype.required_js_list = [];
RenderJSGadget.prototype.declareMethod = function (name, callback) { RenderJSGadget.ready_list = [];
RenderJSGadget.ready = function (callback) {
this.ready_list.push(callback);
};
RenderJSGadget.declareMethod = function (name, callback) {
// // Register the potentially loading javascript // // Register the potentially loading javascript
// var script_element = $('script').last(), // var script_element = $('script').last(),
// src = script_element.attr('src'); // src = script_element.attr('src');
...@@ -48,14 +51,23 @@ ...@@ -48,14 +51,23 @@
// } // }
// } // }
this.constructor.prototype[name] = function () { this.prototype[name] = function () {
return $.when(callback.apply(this, arguments)); var dfr = $.Deferred(),
gadget = this;
$.when(callback.apply(this, arguments))
.done(function () {
dfr.resolveWith(gadget, arguments);
})
.fail(function () {
dfr.rejectWith(gadget, arguments);
});
return dfr.promise();
}; };
// Allow chain // Allow chain
return this; return this;
}; };
RenderJSGadget.prototype RenderJSGadget
.declareMethod('getInterfaceList', function () { .declareMethod('getInterfaceList', function () {
// Returns the list of gadget prototype // Returns the list of gadget prototype
return this.interface_list; return this.interface_list;
...@@ -81,6 +93,64 @@ ...@@ -81,6 +93,64 @@
return this.html; return this.html;
}); });
RenderJSGadget.prototype.declareGadget = function (url, jquery_context) {
var loaded = false,
previous_loading_gadget_promise = loading_gadget_promise,
next_loading_gadget_deferred = $.Deferred(),
dfr = $.Deferred(),
dfr_promise = dfr.promise();
dfr_promise.done(function (created_gadget) {
created_gadget.context.html(created_gadget.constructor.prototype.html);
$.each(created_gadget.constructor.ready_list, function (i, callback) {
callback.apply(created_gadget);
});
});
loading_gadget_promise = next_loading_gadget_deferred.promise();
if (gadget_model_dict.hasOwnProperty(url)) {
loaded = true;
}
renderJS.declareGadgetKlass(url).done(function (Klass) {
var gadget = new Klass();
gadget.context = jquery_context;
if (loaded === false) {
$.when(gadget.getRequiredJSList(), gadget.getRequiredCSSList())
.done(function (js_list, css_list) {
previous_loading_gadget_promise.done(function () {
var result_list = [];
gadget_loading_klass = Klass;
$.each(js_list, function (i, required_url) {
result_list.push(renderJS.declareJS(required_url));
});
$.each(css_list, function (i, required_url) {
result_list.push(renderJS.declareCSS(required_url));
});
$.when.apply(this, result_list).done(function () {
dfr.resolve(gadget);
}).fail(function () {
dfr.reject(gadget);
});
});
}).fail(function () {
dfr.reject(gadget);
});
} else {
dfr.resolve(gadget);
}
}).fail(function () {
dfr.reject();
});
dfr_promise.then(function () {
gadget_loading_klass = undefined;
next_loading_gadget_deferred.resolve();
});
return dfr_promise;
};
methods = { methods = {
loadGadgetFromDom: function () { loadGadgetFromDom: function () {
$(this).find('[data-gadget-path]').each(function (index, value) { $(this).find('[data-gadget-path]').each(function (index, value) {
...@@ -141,20 +211,6 @@ ...@@ -141,20 +211,6 @@
}; };
$.fn.renderJS = function (method) {
var result;
if (methods.hasOwnProperty(method)) {
result = methods[method].apply(
this,
Array.prototype.slice.call(arguments, 1)
);
} else {
$.error('Method ' + method +
' does not exist on jQuery.renderJS');
}
return result;
};
// // Define a local copy of renderJS // // Define a local copy of renderJS
// renderJS = function (selector) { // renderJS = function (selector) {
// // The renderJS object is actually just the init constructor 'enhanced' // // The renderJS object is actually just the init constructor 'enhanced'
...@@ -193,7 +249,24 @@ ...@@ -193,7 +249,24 @@
// }); // });
renderJS = function (selector) { renderJS = function (selector) {
var result;
// if (selector.nodeType) {
// console.log(selector);
// } else {
if (selector === window) {
// window is the this value when loading a javascript file
// In this case, use the current loading gadget constructor
result = gadget_loading_klass;
// } else if ($.isFunction(selector)) {
// console.log(selector);
} else if (selector instanceof RenderJSGadget) {
result = selector;
}
if (result === undefined) {
console.error(selector);
throw new Error("Unknown selector '" + selector + "'"); throw new Error("Unknown selector '" + selector + "'");
}
return result;
}; };
renderJS.declareJS = function (url) { renderJS.declareJS = function (url) {
...@@ -261,6 +334,7 @@ ...@@ -261,6 +334,7 @@
renderJS.declareGadgetKlass = function (url) { renderJS.declareGadgetKlass = function (url) {
var dfr = $.Deferred(), var dfr = $.Deferred(),
parsed_html; parsed_html;
if (gadget_model_dict.hasOwnProperty(url)) { if (gadget_model_dict.hasOwnProperty(url)) {
dfr.resolve(gadget_model_dict[url]); dfr.resolve(gadget_model_dict[url]);
} else { } else {
...@@ -276,6 +350,11 @@ ...@@ -276,6 +350,11 @@
tmp_constructor = function () { tmp_constructor = function () {
RenderJSGadget.call(this); RenderJSGadget.call(this);
}; };
tmp_constructor.ready_list = [];
tmp_constructor.declareMethod =
RenderJSGadget.declareMethod;
tmp_constructor.ready =
RenderJSGadget.ready;
tmp_constructor.prototype = new RenderJSGadget(); tmp_constructor.prototype = new RenderJSGadget();
tmp_constructor.prototype.constructor = tmp_constructor; tmp_constructor.prototype.constructor = tmp_constructor;
tmp_constructor.prototype.path = url; tmp_constructor.prototype.path = url;
...@@ -287,6 +366,7 @@ ...@@ -287,6 +366,7 @@
} }
gadget_model_dict[url] = tmp_constructor; gadget_model_dict[url] = tmp_constructor;
} }
dfr.resolve(gadget_model_dict[url]); dfr.resolve(gadget_model_dict[url]);
} catch (e) { } catch (e) {
dfr.reject(jqXHR, "HTML Parsing failed"); dfr.reject(jqXHR, "HTML Parsing failed");
...@@ -355,1055 +435,1019 @@ ...@@ -355,1055 +435,1019 @@
window.RenderJSGadget = RenderJSGadget; window.RenderJSGadget = RenderJSGadget;
/////////////////////////////////////////////////// ///////////////////////////////////////////////////
// Internal functions // Bootstrap process. Register the self gadget.
/////////////////////////////////////////////////// ///////////////////////////////////////////////////
// declareGadget = function (url, settings) {
// // XXX Return promise
// var dfr = $.Deferred(),
// jqxhr = $.ajax(url, {context: $(this)})
// .done(function (value, textStatus, jqXHR) {
// if ((jqXHR.getResponseHeader("Content-Type") || "")
// === 'text/html') {
// dfr.resolve($.parseGadgetHTML(value), textStatus, jqXHR);
// } else {
// dfr.reject(jqXHR, "Unexpected content type");
// }
// })
// .fail(function (jqXHR, textStatus, errorThrown) {
// dfr.reject(jqXHR, textStatus, errorThrown);
// });
// console.log("Declaring gadget " + url);
// // console.log(settings.context.html());
// return dfr.promise();
// };
/////////////////////////////////////////////////// function bootstrap() {
// jQuery plugin registration var url = window.location.href,
/////////////////////////////////////////////////// tmp_constructor,
$.fn.declareGadget = function (url, settings) { root_gadget,
settings.context = $(this); loading_gadget_deferred = $.Deferred();
return declareGadget(url, settings);
// Create the gadget class for the current url
if (gadget_model_dict.hasOwnProperty(url)) {
throw new Error("bootstrap should not be called twice");
}
// XXX Copy/Paste from declareGadgetKlass
tmp_constructor = function () {
RenderJSGadget.call(this);
}; };
tmp_constructor.declareMethod = RenderJSGadget.declareMethod;
tmp_constructor.ready_list = [];
tmp_constructor.ready = RenderJSGadget.ready;
tmp_constructor.prototype = new RenderJSGadget();
tmp_constructor.prototype.constructor = tmp_constructor;
tmp_constructor.prototype.path = url;
gadget_model_dict[url] = tmp_constructor;
/////////////////////////////////////////////////// // Create the root gadget instance and put it in the loading stack
// Bootstrap process. Register the self gadget. root_gadget = new gadget_model_dict[url]();
/////////////////////////////////////////////////// gadget_loading_klass = tmp_constructor;
// XXX Parse HTML, remember loaded JS, title, css
// XXX Create root gadget
// gadget_model_dict = {window.location: RenderJSGadgetFactory()},
// javascript_registration_dict = {},
// Do not wait for document.ready, as the JS file loading have to be checked loading_gadget_promise = loading_gadget_deferred.promise();
// $.each(document.getElementsByTagName("script"), function (i, elmt) {
// console.log("Set onload " + i);
// elmt.onload = declareGadget;
// });
// newScript = document.createElement('script');
// newScript.type = 'text/javascript';
// newScript.src = $(script).attr('src');
// newScript.onreadystatechange = function () {
// $(document).on('load', declareJavascript).each(function () {
// console.log($(this).attr('src') + " prepared for loading");
// });
// $('body').declareGadget(window.location, {
// scope: "root", $(document).ready(function () {
// }); // XXX HTML properties can only be set when the DOM is fully loaded
var settings = renderJS.parseGadgetHTML($('html')[0].outerHTML),
promise,
key;
for (key in settings) {
if (settings.hasOwnProperty(key)) {
tmp_constructor.prototype[key] = settings[key];
}
}
root_gadget.context = $('body');
promise = $.when(root_gadget.getRequiredJSList(),
root_gadget.getRequiredCSSList())
.done(function (js_list, css_list) {
$.each(js_list, function (i, required_url) {
javascript_registration_dict[required_url] = null;
});
$.each(css_list, function (i, required_url) {
stylesheet_registration_dict[url] = null;
});
$.each(tmp_constructor.ready_list, function (i, callback) {
callback.apply(root_gadget);
});
gadget_loading_klass = undefined;
loading_gadget_deferred.resolve();
});
});
}
bootstrap();
}(document, window, jQuery, DOMParser));
// root_gadget = new RenderJSGadget(); ///**
// $(document).ready(function () { //* By default RenderJs will render all gadgets when page is loaded
// $('link[rel=stylesheet]').each(function (i, link) { //* still it's possible to override this and use explicit gadget rendering.
// root_gadget.required_css_list.push($(link).attr('href')); //*
//* @property RENDERJS_ENABLE_IMPLICIT_GADGET_RENDERING
//* @type {Boolean}
//* @default "true"
//*/
//var RENDERJS_ENABLE_IMPLICIT_GADGET_RENDERING = true;
//
//
///**
//* By default RenderJs will examine and bind all interaction gadgets
//* available.
//*
//* @property RENDERJS_ENABLE_IMPLICIT_INTERACTION_BIND
//* @type {Boolean}
//* @default "true"
//*/
//var RENDERJS_ENABLE_IMPLICIT_INTERACTION_BIND = true;
//
///**
//* By default RenderJs will examine and create all routes
//*
//* @property RENDERJS_ENABLE_IMPLICIT_ROUTE_CREATE
//* @type {Boolean}
//* @default "true"
//*/
//var RENDERJS_ENABLE_IMPLICIT_ROUTE_CREATE = true;
//
///**
//Provides the base RenderJs class
//
//@module RenderJs
//**/
//var RenderJs = (function () {
// // a variable indicating if current gadget loading is over or not
// var is_ready = false, current_gadget;
//
// function setSelfGadget(gadget) {
// /*
// * Only used internally to set current gadget being executed.
// */
// current_gadget = gadget;
// }
//
// return {
//
// init: function () {
// /*
// * Do all initialization
// */
// if (RENDERJS_ENABLE_IMPLICIT_GADGET_RENDERING) {
// RenderJs.bootstrap($('body'));
// }
// var root_gadget = RenderJs.GadgetIndex.getRootGadget();
// if (RENDERJS_ENABLE_IMPLICIT_INTERACTION_BIND ||
// RENDERJS_ENABLE_IMPLICIT_ROUTE_CREATE) {
// // We might have a page without gadgets.
// // Be careful, right now we can be in this case because
// // asynchronous gadget loading is not finished
// if (root_gadget !== undefined) {
// RenderJs.bindReady(
// function () {
// if (RENDERJS_ENABLE_IMPLICIT_INTERACTION_BIND) {
// // examine all Intaction Gadgets and bind accordingly
// RenderJs.InteractionGadget.init();
// }
// if (RENDERJS_ENABLE_IMPLICIT_ROUTE_CREATE) {
// // create all routes between gadgets
// RenderJs.RouteGadget.init();
// }
// }
// );
// }
// }
// },
//
// bootstrap: function (root) {
// /*
// * Load all gadgets for this DOM element
// * (including recursively contained ones)
// */
// var gadget_id, is_gadget;
// gadget_id = root.attr("id");
// is_gadget = root.attr("data-gadget") !== undefined;
// // this will make RenderJs fire "ready" event when all
// // gadgets are loaded.
// RenderJs.setReady(false);
// if (is_gadget && gadget_id !== undefined) {
// // bootstart root gadget only if it is indeed a gadget
// RenderJs.loadGadget(root);
// }
// RenderJs.loadRecursiveGadget(root);
// },
//
// loadRecursiveGadget: function (root) {
// /*
// * Load all contained gadgets inside passed DOM element.
// */
// var gadget_list, gadget, gadget_id, gadget_js;
// gadget_list = root.find("[data-gadget]");
//
// // register all gadget in advance so checkAndTriggerReady
// // can have accurate information for list of all gadgets
// gadget_list.each(function () {
// gadget = $(this);
// gadget_id = gadget.attr("id");
// gadget_js = new RenderJs.Gadget(gadget_id, gadget);
// RenderJs.GadgetIndex.registerGadget(gadget_js);
// }); // });
// $('script[type="text/javascript"]').each(function (i, script) { //
// root_gadget.required_js_list.push($(script).attr('src')); // // Load chilren
// gadget_list.each(function () {
// RenderJs.loadGadget($(this));
// }); // });
// root_gadget.html = $("body").html(); // },
// root_gadget.path = window.location.href; //
// root_gadget.title = $(document).attr('title'); // setGadgetAndRecurse: function (gadget, data) {
// gadget_scope_dict.root = root_gadget; // /*
// * Set gadget data and recursively load it in case it holds another
// * gadgets.
// */
// // set current gadget as being loaded so gadget instance itself
// // knows which gadget it is
// setSelfGadget(RenderJs.GadgetIndex.getGadgetById(gadget.attr("id")));
// gadget.append(data);
// // reset as no longer current gadget
// setSelfGadget(undefined);
// // a gadget may contain sub gadgets
// RenderJs.loadRecursiveGadget(gadget);
// },
// //
// $("body").renderJS('loadGadgetFromDom'); // getSelfGadget: function () {
// /*
// * Get current gadget being loaded
// * This function must be used with care as it relies on
// * Javascript nature of being a single threaded application.
// * Currently current gadget is set in a global RenderJs variable
// * before its HTML is inserted into DOM and if multiple threads
// * were running (which is not the case currently)
// * this could lead to reace conditions and unreliable getSelfGadget
// * results.
// * Additionally this function is available only at gadget's script
// * load time - i.e. it can't be used in after that calls.
// * In this case gagdget can save this value internally.
// */
// return current_gadget;
// },
//
// loadGadget: function (gadget) {
// /*
// * Load gadget's SPECs from URL
// */
// var url, gadget_id, gadget_property, cacheable, cache_id,
// i, gadget_index, gadget_index_id,
// app_cache, data, gadget_js, is_update_gadget_data_running;
//
// url = gadget.attr("data-gadget");
// gadget_id = gadget.attr("id");
// gadget_js = RenderJs.GadgetIndex.getGadgetById(gadget_id);
// gadget_index = RenderJs.GadgetIndex.getGadgetList();
//
// if (gadget_js === undefined) {
// // register gadget in javascript namespace
// //if not already registered
// gadget_js = new RenderJs.Gadget(gadget_id, gadget);
// RenderJs.GadgetIndex.registerGadget(gadget_js);
// }
// if (gadget_js.isReady()) {
// // avoid loading again gadget which was loaded before in same page
// return;
// }
//
// // update Gadget's instance with contents of "data-gadget-property"
// gadget_property = gadget.attr("data-gadget-property");
// if (gadget_property !== undefined) {
// gadget_property = $.parseJSON(gadget_property);
// $.each(gadget_property, function (key, value) {
// gadget_js[key] = value;
// });
// }
// //
// setTimeout(function () { // if (url !== undefined && url !== "") {
// root_gadget.getTitle().done(function (title) { // cacheable = gadget.attr("data-gadget-cacheable");
// console.log("Root: " + title); // cache_id = gadget.attr("data-gadget-cache-id");
// if (cacheable !== undefined && cache_id !== undefined) {
// cacheable = Boolean(parseInt(cacheable, 10));
// }
// //cacheable = false ; // to develop faster
// if (cacheable) {
// // get from cache if possible, use last part from URL as
// // cache_key
// app_cache = RenderJs.Cache.get(cache_id, undefined);
// if (app_cache === undefined || app_cache === null) {
// // not in cache so we pull from network and cache
// $.ajax({
// url: url,
// yourCustomData: {
// "gadget_id": gadget_id,
// "cache_id": cache_id
// },
// success: function (data) {
// cache_id = this.yourCustomData.cache_id;
// gadget_id = this.yourCustomData.gadget_id;
// RenderJs.Cache.set(cache_id, data);
// RenderJs.GadgetIndex.getGadgetById(gadget_id).
// setReady();
// RenderJs.setGadgetAndRecurse(gadget, data);
// RenderJs.checkAndTriggerReady();
// RenderJs.updateGadgetData(gadget);
// }
// });
// } else {
// // get from cache
// data = app_cache;
// gadget_js.setReady();
// this.setGadgetAndRecurse(gadget, data);
// this.checkAndTriggerReady();
// RenderJs.updateGadgetData(gadget);
// }
// } else {
// // not to be cached
// $.ajax({
// url: url,
// yourCustomData: {"gadget_id": gadget_id},
// success: function (data) {
// gadget_id = this.yourCustomData.gadget_id;
// RenderJs.GadgetIndex.getGadgetById(gadget_id).
// setReady();
// RenderJs.setGadgetAndRecurse(gadget, data);
// RenderJs.checkAndTriggerReady();
// RenderJs.updateGadgetData(gadget);
// }
// }); // });
// }
// } else {
// // gadget is an inline (InteractorGadget or one using
// // data-gadget-source / data-gadget-handler) so no need
// // to load it from network
// is_update_gadget_data_running = RenderJs.updateGadgetData(gadget);
// if (!is_update_gadget_data_running) {
// // no update is running so gadget is basically ready
// // if update is running then it should take care and set status
// gadget_js.setReady();
// }
// RenderJs.checkAndTriggerReady();
// }
// },
//
// isReady: function () {
// /*
// * Get rendering status
// */
// return is_ready;
// },
// //
// renderJS("slider").getTitle().done(function (title) { // setReady: function (value) {
// console.log("Slider: " + title); // /*
// * Update rendering status
// */
// is_ready = value;
// },
//
// bindReady: function (ready_function) {
// /*
// * Bind a function on ready gadget loading.
// */
// $("body").one("ready", ready_function);
// },
//
// checkAndTriggerReady: function () {
// /*
// * Trigger "ready" event only if all gadgets were marked as "ready"
// */
// var is_gadget_list_loaded;
// is_gadget_list_loaded = RenderJs.GadgetIndex.isGadgetListLoaded();
// if (is_gadget_list_loaded) {
// if (!RenderJs.isReady()) {
// // backwards compatability with already written code
// RenderJs.GadgetIndex.getRootGadget().getDom().
// trigger("ready");
// // trigger ready on root body element
// $("body").trigger("ready");
// // this set will make sure we fire this event only once
// RenderJs.setReady(true);
// }
// }
// return is_gadget_list_loaded;
// },
//
// updateGadgetData: function (gadget) {
// /*
// * Gadget can be updated from "data-gadget-source" (i.e. a json)
// * and "data-gadget-handler" attributes (i.e. a namespace Javascript)
// */
// var data_source, data_handler;
// data_source = gadget.attr("data-gadget-source");
// data_handler = gadget.attr("data-gadget-handler");
// // acquire data and pass it to method handler
// if (data_source !== undefined && data_source !== "") {
// $.ajax({
// url: data_source,
// dataType: "json",
// yourCustomData: {"data_handler": data_handler,
// "gadget_id": gadget.attr("id")},
// success: function (result) {
// var data_handler, gadget_id;
// data_handler = this.yourCustomData.data_handler;
// gadget_id = this.yourCustomData.gadget_id;
// if (data_handler !== undefined) {
// // eval is not nice to use
// eval(data_handler + "(result)");
// gadget = RenderJs.GadgetIndex.getGadgetById(gadget_id);
// // mark gadget as loaded and fire a check
// // to see if all gadgets are loaded
// gadget.setReady();
// RenderJs.checkAndTriggerReady();
// }
// }
// }); // });
// // asynchronous update happens and respective thread will update
// // status
// return true;
// }
// return false;
// },
//
// addGadget: function (dom_id, gadget_id, gadget, gadget_data_handler,
// gadget_data_source, bootstrap) {
// /*
// * add new gadget and render it
// */
// var html_string, tab_container, tab_gadget;
// tab_container = $('#' + dom_id);
// tab_container.empty();
// html_string = [
// '<div id="' + gadget_id + '"',
// 'data-gadget="' + gadget + '"',
// 'data-gadget-handler="' + gadget_data_handler + '" ',
// 'data-gadget-source="' + gadget_data_source + '"></div>'
// ].join('\n');
//
// tab_container.append(html_string);
// tab_gadget = tab_container.find('#' + gadget_id);
//
// // render new gadget
// if (bootstrap !== false) {
// RenderJs.bootstrap(tab_container);
// }
//
// return tab_gadget;
// },
//
// Cache: (function () {
// /*
// * Generic cache implementation that can fall back to local
// * namespace storage if no "modern" storage like localStorage
// * is available
// */
// return {
// ROOT_CACHE_ID: 'APP_CACHE',
//
// getCacheId: function (cache_id) {
// /*
// * We should have a way to 'purge' localStorage by setting a
// * ROOT_CACHE_ID in all browser instances
// */
// return this.ROOT_CACHE_ID + cache_id;
// },
//
// hasLocalStorage: function () {
// /*
// * Feature test if localStorage is supported
// */
// var mod;
// mod = 'localstorage_test_12345678';
// try {
// localStorage.setItem(mod, mod);
// localStorage.removeItem(mod);
// return true;
// } catch (e) {
// return false;
// }
// },
//
// get: function (cache_id, default_value) {
// /* Get cache key value */
// cache_id = this.getCacheId(cache_id);
// if (this.hasLocalStorage()) {
// return this.LocalStorageCachePlugin.
// get(cache_id, default_value);
// }
// //fallback to javscript namespace cache
// return this.NameSpaceStorageCachePlugin.
// get(cache_id, default_value);
// },
//
// set: function (cache_id, data) {
// /* Set cache key value */
// cache_id = this.getCacheId(cache_id);
// if (this.hasLocalStorage()) {
// this.LocalStorageCachePlugin.set(cache_id, data);
// } else {
// this.NameSpaceStorageCachePlugin.set(cache_id, data);
// }
// },
//
// LocalStorageCachePlugin: (function () {
// /*
// * This plugin saves using HTML5 localStorage.
// */
// return {
// get: function (cache_id, default_value) {
// /* Get cache key value */
// if (localStorage.getItem(cache_id) !== null) {
// return JSON.parse(localStorage.getItem(cache_id));
// }
// return default_value;
// },
//
// set: function (cache_id, data) {
// /* Set cache key value */
// localStorage.setItem(cache_id, JSON.stringify(data));
// }
// };
// }()),
//
// NameSpaceStorageCachePlugin: (function () {
// /*
// * This plugin saves within current page namespace.
// */
// var namespace = {};
//
// return {
// get: function (cache_id, default_value) {
// /* Get cache key value */
// return namespace[cache_id];
// },
//
// set: function (cache_id, data) {
// /* Set cache key value */
// namespace[cache_id] = data;
// }
// };
// }())
// };
// }()),
//
// Gadget: function (gadget_id, dom) {
// /*
// * Javascript Gadget representation
// */
// this.id = gadget_id;
// this.dom = dom;
// this.is_ready = false;
// },
//
// TabbularGadget: (function () {
// /*
// * Generic tabular gadget
// */
// var gadget_list = [];
// return {
// toggleVisibility: function (visible_dom) {
// /*
// * Set tab as active visually and mark as not active rest.
// */
// $(".selected").addClass("not_selected");
// $(".selected").removeClass("selected");
// visible_dom.addClass("selected");
// visible_dom.removeClass("not_selected");
// },
//
// addNewTabGadget: function (dom_id, gadget_id, gadget,
// gadget_data_handler,
// gadget_data_source, bootstrap) {
// /*
// * add new gadget and render it
// */
// var tab_gadget;
// tab_gadget = RenderJs.addGadget(
// dom_id,
// gadget_id,
// gadget,
// gadget_data_handler,
// gadget_data_source,
// bootstrap
// );
//
// // we should unregister all gadgets part of this TabbularGadget
// $.each(gadget_list,
// function (index, gadget_id) {
// var gadget = RenderJs.GadgetIndex.getGadgetById(gadget_id);
// gadget.remove();
// // update list of root gadgets inside TabbularGadget
// gadget_list.splice($.inArray(gadget_id, gadget_list), 1);
// }
// );
// // add it as root gadget
// gadget_list.push(tab_gadget.attr("id"));
// }
// };
// }()),
//
// GadgetIndex: (function () {
// /*
// * Generic gadget index placeholder
// */
// var gadget_list = [];
//
// return {
//
// getGadgetIdListFromDom: function (dom) {
// /*
// * Get list of all gadget's ID from DOM
// */
// var gadget_id_list = [];
// $.each(dom.find('[data-gadget]'),
// function (index, value) {
// gadget_id_list.push($(value).attr("id"));
// }
// );
// return gadget_id_list;
// },
//
// setGadgetList: function (gadget_list_value) {
// /*
// * Set list of registered gadgets
// */
// gadget_list = gadget_list_value;
// },
//
// getGadgetList: function () {
// /*
// * Return list of registered gadgets
// */
// return gadget_list;
// },
//
// registerGadget: function (gadget) {
// /*
// * Register gadget
// */
// if (RenderJs.GadgetIndex.getGadgetById(gadget.id) ===
// undefined) {
// // register only if not already added
// gadget_list.push(gadget);
// }
// },
//
// unregisterGadget: function (gadget) {
// /*
// * Unregister gadget
// */
// var index = $.inArray(gadget, gadget_list);
// if (index !== -1) {
// gadget_list.splice(index, 1);
// }
// },
//
// getGadgetById: function (gadget_id) {
// /*
// * Get gadget javascript representation by its Id
// */
// var gadget;
// gadget = undefined;
// $(RenderJs.GadgetIndex.getGadgetList()).each(
// function (index, value) {
// if (value.getId() === gadget_id) {
// gadget = value;
// }
// }
// );
// return gadget;
// },
//
// getRootGadget: function () {
// /*
// * Return root gadget (always first one in list)
// */
// return this.getGadgetList()[0];
// },
//
// isGadgetListLoaded: function () {
// /*
// * Return True if all gadgets were loaded from network or
// * cache
// */
// var result;
// result = true;
// $(this.getGadgetList()).each(
// function (index, value) {
// if (value.isReady() === false) {
// result = false;
// }
// }
// );
// return result;
// }
// };
// }()),
// //
// renderJS("first").getTitle().done(function (title) { // GadgetCatalog : (function () {
// console.log("First: " + title); // /*
// * Gadget catalog provides API to get
// * list of gadgets from a repository
// */
// var cache_id = "setGadgetIndexUrlList";
//
// function updateGadgetIndexFromURL(url) {
// // split to base and document url
// var url_list = url.split('/'),
// document_url = url_list[url_list.length - 1],
// d = url_list.splice($.inArray(document_url, url_list), 1),
// base_url = url_list.join('/'),
// web_dav = jIO.newJio({
// "type": "dav",
// "username": "",
// "password": "",
// "url": base_url
// });
// web_dav.get(document_url,
// function (err, response) {
// RenderJs.Cache.set(url, response);
// }); // });
// }
// //
// renderJS("second").getTitle().done(function (title) { // return {
// console.log("Second: " + title); // updateGadgetIndex: function () {
// /*
// * Update gadget index from all configured remote repositories.
// */
// $.each(RenderJs.GadgetCatalog.getGadgetIndexUrlList(),
// function (index, value) {
// updateGadgetIndexFromURL(value);
// }); // });
// }, 500); // },
//
// setGadgetIndexUrlList: function (url_list) {
// /*
// * Set list of Gadget Index repositories.
// */
// // store in Cache (html5 storage)
// RenderJs.Cache.set(cache_id, url_list);
// },
// //
// getGadgetIndexUrlList: function () {
// /*
// * Get list of Gadget Index repositories.
// */
// // get from Cache (html5 storage)
// return RenderJs.Cache.get(cache_id, undefined);
// },
// //
// // XXX Display sub gadgets title // getGadgetListThatProvide: function (service) {
// /*
// * Return list of all gadgets that providen a given service.
// * Read this list from data structure created in HTML5 local
// * storage by updateGadgetIndexFromURL
// */
// // get from Cache stored index and itterate over it
// // to find matching ones
// var gadget_list = [];
// $.each(RenderJs.GadgetCatalog.getGadgetIndexUrlList(),
// function (index, url) {
// // get repos from cache
// var cached_repo = RenderJs.Cache.get(url);
// $.each(cached_repo.gadget_list,
// function (index, gadget) {
// if ($.inArray(service, gadget.service_list) > -1) {
// // gadget provides a service, add to list
// gadget_list.push(gadget);
// }
// }
// );
// }); // });
// return gadget_list;
// },
// $(document).ready(function () { //
// root_gadget = $('body').declareGadget(window.location, { // registerServiceList: function (gadget, service_list) {
// scope: "root", // /*
// * Register a service provided by a gadget.
// */
// }
// };
// }()),
//
// InteractionGadget : (function () {
// /*
// * Basic gadget interaction gadget implementation.
// */
// return {
//
// init: function (force) {
// /*
// * Inspect DOM and initialize this gadget
// */
// var dom_list, gadget_id;
// if (force === 1) {
// // we explicitly want to re-init elements even if already this
// // is done before
// dom_list = $("div[data-gadget-connection]");
// } else {
// // XXX: improve and save 'bound' on javascript representation
// // of a gadget not DOM
// dom_list = $("div[data-gadget-connection]")
// .filter(function () {
// return $(this).data("bound") !== true;
// })
// .data('bound', true);
// }
// dom_list.each(function (index, element) {
// RenderJs.InteractionGadget.bind($(element));
// }); // });
// },
//
// bind: function (gadget_dom) {
// /*
// * Bind event between gadgets.
// */
// var gadget_id, gadget_connection_list,
// createMethodInteraction = function (
// original_source_method_id,
// source_gadget_id,
// source_method_id,
// destination_gadget_id,
// destination_method_id
// ) {
// var interaction = function () {
// // execute source method
// RenderJs.GadgetIndex.getGadgetById(
// source_gadget_id
// )[original_source_method_id].
// apply(null, arguments);
// // call trigger so bind can be asynchronously called
// RenderJs.GadgetIndex.getGadgetById(
// destination_gadget_id
// ).dom.trigger(source_method_id);
// };
// return interaction;
// },
// createTriggerInteraction = function (
// destination_gadget_id,
// destination_method_id
// ) {
// var interaction = function () {
// RenderJs.GadgetIndex.getGadgetById(
// destination_gadget_id
// )[destination_method_id].
// apply(null, arguments);
// };
// return interaction;
// };
// gadget_id = gadget_dom.attr("id");
// gadget_connection_list =
// gadget_dom.attr("data-gadget-connection");
// gadget_connection_list = $.parseJSON(gadget_connection_list);
// $.each(gadget_connection_list, function (key, value) {
// var source,
// source_gadget_id,
// source_method_id,
// source_gadget,
// destination,
// destination_gadget_id,
// destination_method_id,
// destination_gadget,
// original_source_method_id;
// source = value.source.split(".");
// source_gadget_id = source[0];
// source_method_id = source[1];
// source_gadget = RenderJs.GadgetIndex.
// getGadgetById(source_gadget_id);
//
// destination = value.destination.split(".");
// destination_gadget_id = destination[0];
// destination_method_id = destination[1];
// destination_gadget = RenderJs.GadgetIndex.
// getGadgetById(destination_gadget_id);
//
// if (source_gadget.hasOwnProperty(source_method_id)) {
// // direct javascript use case
// original_source_method_id = "original_" +
// source_method_id;
// source_gadget[original_source_method_id] =
// source_gadget[source_method_id];
// source_gadget[source_method_id] =
// createMethodInteraction(
// original_source_method_id,
// source_gadget_id,
// source_method_id,
// destination_gadget_id,
// destination_method_id
// );
// // we use html custom events for asyncronous method call so
// // bind destination_gadget to respective event
// destination_gadget.dom.bind(
// source_method_id,
// createTriggerInteraction(
// destination_gadget_id,
// destination_method_id
// )
// );
// } else {
// // this is a custom event attached to HTML gadget
// // representation
// source_gadget.dom.bind(
// source_method_id,
// createTriggerInteraction(
// destination_gadget_id,
// destination_method_id
// )
// );
// }
// }); // });
// // XXX Load gadgets defined in the html // }
// $('body').renderJS('loadGadgetFromDom'); // };
// }()),
}(document, window, jQuery, DOMParser)); //
// RouteGadget : (function () {
// /*
/** // * A gadget that defines possible routes (i.e. URL changes)
* By default RenderJs will render all gadgets when page is loaded // * between gadgets.
* still it's possible to override this and use explicit gadget rendering. // */
* // var route_list = [];
* @property RENDERJS_ENABLE_IMPLICIT_GADGET_RENDERING // return {
* @type {Boolean} //
* @default "true" // init: function () {
*/ // /*
var RENDERJS_ENABLE_IMPLICIT_GADGET_RENDERING = true; // * Inspect DOM and initialize this gadget
// */
// $("div[data-gadget-route]").each(function (index, element) {
/** // RenderJs.RouteGadget.route($(element));
* By default RenderJs will examine and bind all interaction gadgets // });
* available. // },
* //
* @property RENDERJS_ENABLE_IMPLICIT_INTERACTION_BIND // route: function (gadget_dom) {
* @type {Boolean} // /*
* @default "true" // * Create routes between gadgets.
*/ // */
var RENDERJS_ENABLE_IMPLICIT_INTERACTION_BIND = true; // var body = $("body"),
// handler_func,
/** // priority,
* By default RenderJs will examine and create all routes // gadget_route_list = gadget_dom.attr("data-gadget-route");
* // gadget_route_list = $.parseJSON(gadget_route_list);
* @property RENDERJS_ENABLE_IMPLICIT_ROUTE_CREATE // $.each(gadget_route_list, function (key, gadget_route) {
* @type {Boolean} // handler_func = function () {
* @default "true" // var gadget_id = gadget_route.destination.split('.')[0],
*/ // method_id = gadget_route.destination.split('.')[1],
var RENDERJS_ENABLE_IMPLICIT_ROUTE_CREATE = true; // gadget = RenderJs.GadgetIndex.getGadgetById(gadget_id);
// // set gadget value so getSelfGadget can work
/** // setSelfGadget(gadget);
Provides the base RenderJs class // gadget[method_id].apply(null, arguments);
// // reset as no longer needed
@module RenderJs // setSelfGadget(undefined);
**/ // };
var RenderJs = (function () { // // add route itself
// a variable indicating if current gadget loading is over or not // priority = gadget_route.priority;
var is_ready = false, current_gadget; // if (priority === undefined) {
// // default is 1 -i.e.first level
function setSelfGadget(gadget) { // priority = 1;
/* // }
* Only used internally to set current gadget being executed. // RenderJs.RouteGadget.add(gadget_route.source,
*/ // handler_func, priority);
current_gadget = gadget; // });
} // },
//
return { // add: function (path, handler_func, priority) {
// /*
init: function () { // * Add a route between path (hashable) and a handler function
/* // * (part of Gadget's API).
* Do all initialization // */
*/ // var body = $("body");
if (RENDERJS_ENABLE_IMPLICIT_GADGET_RENDERING) { // body
RenderJs.bootstrap($('body')); // .route("add", path, 1)
} // .done(handler_func);
var root_gadget = RenderJs.GadgetIndex.getRootGadget(); // // save locally
if (RENDERJS_ENABLE_IMPLICIT_INTERACTION_BIND || // route_list.push({"path": path,
RENDERJS_ENABLE_IMPLICIT_ROUTE_CREATE) { // "handler_func": handler_func,
// We might have a page without gadgets. // "priority": priority});
// Be careful, right now we can be in this case because // },
// asynchronous gadget loading is not finished //
if (root_gadget !== undefined) { // go: function (path, handler_func, priority) {
RenderJs.bindReady( // /*
function () { // * Go a route.
if (RENDERJS_ENABLE_IMPLICIT_INTERACTION_BIND) { // */
// examine all Intaction Gadgets and bind accordingly // var body = $("body");
RenderJs.InteractionGadget.init(); // body
} // .route("go", path, priority)
if (RENDERJS_ENABLE_IMPLICIT_ROUTE_CREATE) { // .fail(handler_func);
// create all routes between gadgets // },
RenderJs.RouteGadget.init(); //
} // remove: function (path) {
} // /*
); // * Remove a route.
} // */
} //
}, // // XXX: implement remove a route when route.js supports it
// },
bootstrap: function (root) { //
/* // getRouteList: function () {
* Load all gadgets for this DOM element // /*
* (including recursively contained ones) // * Get list of all router
*/ // */
var gadget_id, is_gadget; // return route_list;
gadget_id = root.attr("id"); // }
is_gadget = root.attr("data-gadget") !== undefined; // };
// this will make RenderJs fire "ready" event when all // }())
// gadgets are loaded. // };
RenderJs.setReady(false); // }());
if (is_gadget && gadget_id !== undefined) { //
// bootstart root gadget only if it is indeed a gadget //// Define Gadget prototype
RenderJs.loadGadget(root); //RenderJs.Gadget.prototype.getId = function () {
} // return this.id;
RenderJs.loadRecursiveGadget(root); //};
}, //
//RenderJs.Gadget.prototype.getDom = function () {
loadRecursiveGadget: function (root) { // return this.dom;
/* //};
* Load all contained gadgets inside passed DOM element. //
*/ //RenderJs.Gadget.prototype.isReady = function () {
var gadget_list, gadget, gadget_id, gadget_js; // /*
gadget_list = root.find("[data-gadget]"); // * Return True if remote gadget is loaded into DOM.
// */
// register all gadget in advance so checkAndTriggerReady // return this.is_ready;
// can have accurate information for list of all gadgets //};
gadget_list.each(function () { //
gadget = $(this); //RenderJs.Gadget.prototype.setReady = function () {
gadget_id = gadget.attr("id"); // /*
gadget_js = new RenderJs.Gadget(gadget_id, gadget); // * Return True if remote gadget is loaded into DOM.
RenderJs.GadgetIndex.registerGadget(gadget_js); // */
}); // this.is_ready = true;
//};
// Load chilren //
gadget_list.each(function () { //RenderJs.Gadget.prototype.remove = function () {
RenderJs.loadGadget($(this)); // /*
}); // * Remove gadget (including its DOM element).
}, // */
// var gadget;
setGadgetAndRecurse: function (gadget, data) { // // unregister root from GadgetIndex
/* // RenderJs.GadgetIndex.unregisterGadget(this);
* Set gadget data and recursively load it in case it holds another // // gadget might contain sub gadgets so before remove entire
* gadgets. // // DOM we must unregister them from GadgetIndex
*/ // this.getDom().find("[data-gadget]").each(function () {
// set current gadget as being loaded so gadget instance itself // gadget = RenderJs.GadgetIndex.getGadgetById($(this).attr("id"));
// knows which gadget it is // RenderJs.GadgetIndex.unregisterGadget(gadget);
setSelfGadget(RenderJs.GadgetIndex.getGadgetById(gadget.attr("id"))); // });
gadget.append(data); // // remove root's entire DOM element
// reset as no longer current gadget // $(this.getDom()).remove();
setSelfGadget(undefined); //};
// a gadget may contain sub gadgets
RenderJs.loadRecursiveGadget(gadget);
},
getSelfGadget: function () {
/*
* Get current gadget being loaded
* This function must be used with care as it relies on
* Javascript nature of being a single threaded application.
* Currently current gadget is set in a global RenderJs variable
* before its HTML is inserted into DOM and if multiple threads
* were running (which is not the case currently)
* this could lead to reace conditions and unreliable getSelfGadget
* results.
* Additionally this function is available only at gadget's script
* load time - i.e. it can't be used in after that calls.
* In this case gagdget can save this value internally.
*/
return current_gadget;
},
loadGadget: function (gadget) {
/*
* Load gadget's SPECs from URL
*/
var url, gadget_id, gadget_property, cacheable, cache_id,
i, gadget_index, gadget_index_id,
app_cache, data, gadget_js, is_update_gadget_data_running;
url = gadget.attr("data-gadget");
gadget_id = gadget.attr("id");
gadget_js = RenderJs.GadgetIndex.getGadgetById(gadget_id);
gadget_index = RenderJs.GadgetIndex.getGadgetList();
if (gadget_js === undefined) {
// register gadget in javascript namespace if not already registered
gadget_js = new RenderJs.Gadget(gadget_id, gadget);
RenderJs.GadgetIndex.registerGadget(gadget_js);
}
if (gadget_js.isReady()) {
// avoid loading again gadget which was loaded before in same page
return;
}
// update Gadget's instance with contents of "data-gadget-property"
gadget_property = gadget.attr("data-gadget-property");
if (gadget_property !== undefined) {
gadget_property = $.parseJSON(gadget_property);
$.each(gadget_property, function (key, value) {
gadget_js[key] = value;
});
}
if (url !== undefined && url !== "") {
cacheable = gadget.attr("data-gadget-cacheable");
cache_id = gadget.attr("data-gadget-cache-id");
if (cacheable !== undefined && cache_id !== undefined) {
cacheable = Boolean(parseInt(cacheable, 10));
}
//cacheable = false ; // to develop faster
if (cacheable) {
// get from cache if possible, use last part from URL as
// cache_key
app_cache = RenderJs.Cache.get(cache_id, undefined);
if (app_cache === undefined || app_cache === null) {
// not in cache so we pull from network and cache
$.ajax({
url: url,
yourCustomData: {
"gadget_id": gadget_id,
"cache_id": cache_id
},
success: function (data) {
cache_id = this.yourCustomData.cache_id;
gadget_id = this.yourCustomData.gadget_id;
RenderJs.Cache.set(cache_id, data);
RenderJs.GadgetIndex.getGadgetById(gadget_id).
setReady();
RenderJs.setGadgetAndRecurse(gadget, data);
RenderJs.checkAndTriggerReady();
RenderJs.updateGadgetData(gadget);
}
});
} else {
// get from cache
data = app_cache;
gadget_js.setReady();
this.setGadgetAndRecurse(gadget, data);
this.checkAndTriggerReady();
RenderJs.updateGadgetData(gadget);
}
} else {
// not to be cached
$.ajax({
url: url,
yourCustomData: {"gadget_id": gadget_id},
success: function (data) {
gadget_id = this.yourCustomData.gadget_id;
RenderJs.GadgetIndex.getGadgetById(gadget_id).
setReady();
RenderJs.setGadgetAndRecurse(gadget, data);
RenderJs.checkAndTriggerReady();
RenderJs.updateGadgetData(gadget);
}
});
}
} else {
// gadget is an inline (InteractorGadget or one using
// data-gadget-source / data-gadget-handler) so no need
// to load it from network
is_update_gadget_data_running = RenderJs.updateGadgetData(gadget);
if (!is_update_gadget_data_running) {
// no update is running so gadget is basically ready
// if update is running then it should take care and set status
gadget_js.setReady();
}
RenderJs.checkAndTriggerReady();
}
},
isReady: function () {
/*
* Get rendering status
*/
return is_ready;
},
setReady: function (value) {
/*
* Update rendering status
*/
is_ready = value;
},
bindReady: function (ready_function) {
/*
* Bind a function on ready gadget loading.
*/
$("body").one("ready", ready_function);
},
checkAndTriggerReady: function () {
/*
* Trigger "ready" event only if all gadgets were marked as "ready"
*/
var is_gadget_list_loaded;
is_gadget_list_loaded = RenderJs.GadgetIndex.isGadgetListLoaded();
if (is_gadget_list_loaded) {
if (!RenderJs.isReady()) {
// backwards compatability with already written code
RenderJs.GadgetIndex.getRootGadget().getDom().
trigger("ready");
// trigger ready on root body element
$("body").trigger("ready");
// this set will make sure we fire this event only once
RenderJs.setReady(true);
}
}
return is_gadget_list_loaded;
},
updateGadgetData: function (gadget) {
/*
* Gadget can be updated from "data-gadget-source" (i.e. a json)
* and "data-gadget-handler" attributes (i.e. a namespace Javascript)
*/
var data_source, data_handler;
data_source = gadget.attr("data-gadget-source");
data_handler = gadget.attr("data-gadget-handler");
// acquire data and pass it to method handler
if (data_source !== undefined && data_source !== "") {
$.ajax({
url: data_source,
dataType: "json",
yourCustomData: {"data_handler": data_handler,
"gadget_id": gadget.attr("id")},
success: function (result) {
var data_handler, gadget_id;
data_handler = this.yourCustomData.data_handler;
gadget_id = this.yourCustomData.gadget_id;
if (data_handler !== undefined) {
// eval is not nice to use
eval(data_handler + "(result)");
gadget = RenderJs.GadgetIndex.getGadgetById(gadget_id);
// mark gadget as loaded and fire a check
// to see if all gadgets are loaded
gadget.setReady();
RenderJs.checkAndTriggerReady();
}
}
});
// asynchronous update happens and respective thread will update
// status
return true;
}
return false;
},
addGadget: function (dom_id, gadget_id, gadget, gadget_data_handler,
gadget_data_source, bootstrap) {
/*
* add new gadget and render it
*/
var html_string, tab_container, tab_gadget;
tab_container = $('#' + dom_id);
tab_container.empty();
html_string = [
'<div id="' + gadget_id + '"',
'data-gadget="' + gadget + '"',
'data-gadget-handler="' + gadget_data_handler + '" ',
'data-gadget-source="' + gadget_data_source + '"></div>'
].join('\n');
tab_container.append(html_string);
tab_gadget = tab_container.find('#' + gadget_id);
// render new gadget
if (bootstrap !== false) {
RenderJs.bootstrap(tab_container);
}
return tab_gadget;
},
Cache: (function () {
/*
* Generic cache implementation that can fall back to local
* namespace storage if no "modern" storage like localStorage
* is available
*/
return {
ROOT_CACHE_ID: 'APP_CACHE',
getCacheId: function (cache_id) {
/*
* We should have a way to 'purge' localStorage by setting a
* ROOT_CACHE_ID in all browser instances
*/
return this.ROOT_CACHE_ID + cache_id;
},
hasLocalStorage: function () {
/*
* Feature test if localStorage is supported
*/
var mod;
mod = 'localstorage_test_12345678';
try {
localStorage.setItem(mod, mod);
localStorage.removeItem(mod);
return true;
} catch (e) {
return false;
}
},
get: function (cache_id, default_value) {
/* Get cache key value */
cache_id = this.getCacheId(cache_id);
if (this.hasLocalStorage()) {
return this.LocalStorageCachePlugin.
get(cache_id, default_value);
}
//fallback to javscript namespace cache
return this.NameSpaceStorageCachePlugin.
get(cache_id, default_value);
},
set: function (cache_id, data) {
/* Set cache key value */
cache_id = this.getCacheId(cache_id);
if (this.hasLocalStorage()) {
this.LocalStorageCachePlugin.set(cache_id, data);
} else {
this.NameSpaceStorageCachePlugin.set(cache_id, data);
}
},
LocalStorageCachePlugin: (function () {
/*
* This plugin saves using HTML5 localStorage.
*/
return {
get: function (cache_id, default_value) {
/* Get cache key value */
if (localStorage.getItem(cache_id) !== null) {
return JSON.parse(localStorage.getItem(cache_id));
}
return default_value;
},
set: function (cache_id, data) {
/* Set cache key value */
localStorage.setItem(cache_id, JSON.stringify(data));
}
};
}()),
NameSpaceStorageCachePlugin: (function () {
/*
* This plugin saves within current page namespace.
*/
var namespace = {};
return {
get: function (cache_id, default_value) {
/* Get cache key value */
return namespace[cache_id];
},
set: function (cache_id, data) {
/* Set cache key value */
namespace[cache_id] = data;
}
};
}())
};
}()),
Gadget: function (gadget_id, dom) {
/*
* Javascript Gadget representation
*/
this.id = gadget_id;
this.dom = dom;
this.is_ready = false;
},
TabbularGadget: (function () {
/*
* Generic tabular gadget
*/
var gadget_list = [];
return {
toggleVisibility: function (visible_dom) {
/*
* Set tab as active visually and mark as not active rest.
*/
$(".selected").addClass("not_selected");
$(".selected").removeClass("selected");
visible_dom.addClass("selected");
visible_dom.removeClass("not_selected");
},
addNewTabGadget: function (dom_id, gadget_id, gadget,
gadget_data_handler,
gadget_data_source, bootstrap) {
/*
* add new gadget and render it
*/
var tab_gadget;
tab_gadget = RenderJs.addGadget(
dom_id,
gadget_id,
gadget,
gadget_data_handler,
gadget_data_source,
bootstrap
);
// we should unregister all gadgets part of this TabbularGadget
$.each(gadget_list,
function (index, gadget_id) {
var gadget = RenderJs.GadgetIndex.getGadgetById(gadget_id);
gadget.remove();
// update list of root gadgets inside TabbularGadget
gadget_list.splice($.inArray(gadget_id, gadget_list), 1);
}
);
// add it as root gadget
gadget_list.push(tab_gadget.attr("id"));
}
};
}()),
GadgetIndex: (function () {
/*
* Generic gadget index placeholder
*/
var gadget_list = [];
return {
getGadgetIdListFromDom: function (dom) {
/*
* Get list of all gadget's ID from DOM
*/
var gadget_id_list = [];
$.each(dom.find('[data-gadget]'),
function (index, value) {
gadget_id_list.push($(value).attr("id"));
}
);
return gadget_id_list;
},
setGadgetList: function (gadget_list_value) {
/*
* Set list of registered gadgets
*/
gadget_list = gadget_list_value;
},
getGadgetList: function () {
/*
* Return list of registered gadgets
*/
return gadget_list;
},
registerGadget: function (gadget) {
/*
* Register gadget
*/
if (RenderJs.GadgetIndex.getGadgetById(gadget.id) === undefined) {
// register only if not already added
gadget_list.push(gadget);
}
},
unregisterGadget: function (gadget) {
/*
* Unregister gadget
*/
var index = $.inArray(gadget, gadget_list);
if (index !== -1) {
gadget_list.splice(index, 1);
}
},
getGadgetById: function (gadget_id) {
/*
* Get gadget javascript representation by its Id
*/
var gadget;
gadget = undefined;
$(RenderJs.GadgetIndex.getGadgetList()).each(
function (index, value) {
if (value.getId() === gadget_id) {
gadget = value;
}
}
);
return gadget;
},
getRootGadget: function () {
/*
* Return root gadget (always first one in list)
*/
return this.getGadgetList()[0];
},
isGadgetListLoaded: function () {
/*
* Return True if all gadgets were loaded from network or
* cache
*/
var result;
result = true;
$(this.getGadgetList()).each(
function (index, value) {
if (value.isReady() === false) {
result = false;
}
}
);
return result;
}
};
}()),
GadgetCatalog : (function () {
/*
* Gadget catalog provides API to get list of gadgets from a repository
*/
var cache_id = "setGadgetIndexUrlList";
function updateGadgetIndexFromURL(url) {
// split to base and document url
var url_list = url.split('/'),
document_url = url_list[url_list.length - 1],
d = url_list.splice($.inArray(document_url, url_list), 1),
base_url = url_list.join('/'),
web_dav = jIO.newJio({
"type": "dav",
"username": "",
"password": "",
"url": base_url
});
web_dav.get(document_url,
function (err, response) {
RenderJs.Cache.set(url, response);
});
}
return {
updateGadgetIndex: function () {
/*
* Update gadget index from all configured remote repositories.
*/
$.each(RenderJs.GadgetCatalog.getGadgetIndexUrlList(),
function (index, value) {
updateGadgetIndexFromURL(value);
});
},
setGadgetIndexUrlList: function (url_list) {
/*
* Set list of Gadget Index repositories.
*/
// store in Cache (html5 storage)
RenderJs.Cache.set(cache_id, url_list);
},
getGadgetIndexUrlList: function () {
/*
* Get list of Gadget Index repositories.
*/
// get from Cache (html5 storage)
return RenderJs.Cache.get(cache_id, undefined);
},
getGadgetListThatProvide: function (service) {
/*
* Return list of all gadgets that providen a given service.
* Read this list from data structure created in HTML5 local
* storage by updateGadgetIndexFromURL
*/
// get from Cache stored index and itterate over it
// to find matching ones
var gadget_list = [];
$.each(RenderJs.GadgetCatalog.getGadgetIndexUrlList(),
function (index, url) {
// get repos from cache
var cached_repo = RenderJs.Cache.get(url);
$.each(cached_repo.gadget_list,
function (index, gadget) {
if ($.inArray(service, gadget.service_list) > -1) {
// gadget provides a service, add to list
gadget_list.push(gadget);
}
}
);
});
return gadget_list;
},
registerServiceList: function (gadget, service_list) {
/*
* Register a service provided by a gadget.
*/
}
};
}()),
InteractionGadget : (function () {
/*
* Basic gadget interaction gadget implementation.
*/
return {
init: function (force) {
/*
* Inspect DOM and initialize this gadget
*/
var dom_list, gadget_id;
if (force === 1) {
// we explicitly want to re-init elements even if already this
// is done before
dom_list = $("div[data-gadget-connection]");
} else {
// XXX: improve and save 'bound' on javascript representation
// of a gadget not DOM
dom_list = $("div[data-gadget-connection]")
.filter(function () {
return $(this).data("bound") !== true;
})
.data('bound', true);
}
dom_list.each(function (index, element) {
RenderJs.InteractionGadget.bind($(element));
});
},
bind: function (gadget_dom) {
/*
* Bind event between gadgets.
*/
var gadget_id, gadget_connection_list,
createMethodInteraction = function (
original_source_method_id,
source_gadget_id,
source_method_id,
destination_gadget_id,
destination_method_id
) {
var interaction = function () {
// execute source method
RenderJs.GadgetIndex.getGadgetById(
source_gadget_id
)[original_source_method_id].
apply(null, arguments);
// call trigger so bind can be asynchronously called
RenderJs.GadgetIndex.getGadgetById(
destination_gadget_id
).dom.trigger(source_method_id);
};
return interaction;
},
createTriggerInteraction = function (
destination_gadget_id,
destination_method_id
) {
var interaction = function () {
RenderJs.GadgetIndex.getGadgetById(
destination_gadget_id
)[destination_method_id].
apply(null, arguments);
};
return interaction;
};
gadget_id = gadget_dom.attr("id");
gadget_connection_list = gadget_dom.attr("data-gadget-connection");
gadget_connection_list = $.parseJSON(gadget_connection_list);
$.each(gadget_connection_list, function (key, value) {
var source,
source_gadget_id,
source_method_id,
source_gadget,
destination,
destination_gadget_id,
destination_method_id,
destination_gadget,
original_source_method_id;
source = value.source.split(".");
source_gadget_id = source[0];
source_method_id = source[1];
source_gadget = RenderJs.GadgetIndex.
getGadgetById(source_gadget_id);
destination = value.destination.split(".");
destination_gadget_id = destination[0];
destination_method_id = destination[1];
destination_gadget = RenderJs.GadgetIndex.
getGadgetById(destination_gadget_id);
if (source_gadget.hasOwnProperty(source_method_id)) {
// direct javascript use case
original_source_method_id = "original_" +
source_method_id;
source_gadget[original_source_method_id] =
source_gadget[source_method_id];
source_gadget[source_method_id] =
createMethodInteraction(
original_source_method_id,
source_gadget_id,
source_method_id,
destination_gadget_id,
destination_method_id
);
// we use html custom events for asyncronous method call so
// bind destination_gadget to respective event
destination_gadget.dom.bind(
source_method_id,
createTriggerInteraction(
destination_gadget_id,
destination_method_id
)
);
} else {
// this is a custom event attached to HTML gadget
// representation
source_gadget.dom.bind(
source_method_id,
createTriggerInteraction(
destination_gadget_id,
destination_method_id
)
);
}
});
}
};
}()),
RouteGadget : (function () {
/*
* A gadget that defines possible routes (i.e. URL changes)
* between gadgets.
*/
var route_list = [];
return {
init: function () {
/*
* Inspect DOM and initialize this gadget
*/
$("div[data-gadget-route]").each(function (index, element) {
RenderJs.RouteGadget.route($(element));
});
},
route: function (gadget_dom) {
/*
* Create routes between gadgets.
*/
var body = $("body"),
handler_func,
priority,
gadget_route_list = gadget_dom.attr("data-gadget-route");
gadget_route_list = $.parseJSON(gadget_route_list);
$.each(gadget_route_list, function (key, gadget_route) {
handler_func = function () {
var gadget_id = gadget_route.destination.split('.')[0],
method_id = gadget_route.destination.split('.')[1],
gadget = RenderJs.GadgetIndex.getGadgetById(gadget_id);
// set gadget value so getSelfGadget can work
setSelfGadget(gadget);
gadget[method_id].apply(null, arguments);
// reset as no longer needed
setSelfGadget(undefined);
};
// add route itself
priority = gadget_route.priority;
if (priority === undefined) {
// default is 1 -i.e.first level
priority = 1;
}
RenderJs.RouteGadget.add(gadget_route.source,
handler_func, priority);
});
},
add: function (path, handler_func, priority) {
/*
* Add a route between path (hashable) and a handler function
* (part of Gadget's API).
*/
var body = $("body");
body
.route("add", path, 1)
.done(handler_func);
// save locally
route_list.push({"path": path,
"handler_func": handler_func,
"priority": priority});
},
go: function (path, handler_func, priority) {
/*
* Go a route.
*/
var body = $("body");
body
.route("go", path, priority)
.fail(handler_func);
},
remove: function (path) {
/*
* Remove a route.
*/
// XXX: implement remove a route when route.js supports it
},
getRouteList: function () {
/*
* Get list of all router
*/
return route_list;
}
};
}())
};
}());
// Define Gadget prototype
RenderJs.Gadget.prototype.getId = function () {
return this.id;
};
RenderJs.Gadget.prototype.getDom = function () {
return this.dom;
};
RenderJs.Gadget.prototype.isReady = function () {
/*
* Return True if remote gadget is loaded into DOM.
*/
return this.is_ready;
};
RenderJs.Gadget.prototype.setReady = function () {
/*
* Return True if remote gadget is loaded into DOM.
*/
this.is_ready = true;
};
RenderJs.Gadget.prototype.remove = function () {
/*
* Remove gadget (including its DOM element).
*/
var gadget;
// unregister root from GadgetIndex
RenderJs.GadgetIndex.unregisterGadget(this);
// gadget might contain sub gadgets so before remove entire
// DOM we must unregister them from GadgetIndex
this.getDom().find("[data-gadget]").each(function () {
gadget = RenderJs.GadgetIndex.getGadgetById($(this).attr("id"));
RenderJs.GadgetIndex.unregisterGadget(gadget);
});
// remove root's entire DOM element
$(this.getDom()).remove();
};
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