diff --git a/src/css/resting.css b/src/css/resting.css
index 8f71bf6..b09ea4a 100644
--- a/src/css/resting.css
+++ b/src/css/resting.css
@@ -36,10 +36,17 @@ body {
z-index: 100;
}
+.context-dialog {
+ position: absolute;
+ top: 100px;
+ width: 700px;
+ z-index: 100;
+}
+
.bookmark {
display: block;
padding: 10px 15px;
- cursor: pointer;
+ cursor: pointer;
}
.folder {
@@ -54,7 +61,7 @@ a.bookmark, a.folder {
}
a.folder:hover, a.bookmark:hover, .folder .panel-heading:hover {
- background-color: #337ab7;
+ background-color: #337ab7;
color: #fff;
}
@@ -82,3 +89,39 @@ label.disabled {
color: gray;
}
+.dropdown-submenu{ position: relative; }
+
+.dropdown-submenu>.dropdown-menu{
+ top:0;
+ left:100%;
+ margin-top:-6px;
+ margin-left:-1px;
+ -webkit-border-radius:0 6px 6px 6px;
+ -moz-border-radius:0 6px 6px 6px;
+ border-radius:0 6px 6px 6px;
+}
+
+.dropdown-submenu>a:after{
+ display:block;
+ content:" ";
+ float:right;
+ width:0;
+ height:0;
+ border-color:transparent;
+ border-style:solid;
+ border-width:5px 0 5px 5px;
+ border-left-color:#cccccc;
+ margin-top:5px;margin-right:-10px;
+}
+.dropdown-submenu:hover>a:after{
+ border-left-color:#555;
+}
+.dropdown-submenu.pull-left{ float: none; }
+.dropdown-submenu.pull-left>.dropdown-menu{
+ left: -100%;
+ margin-left: 10px;
+ -webkit-border-radius: 6px 0 6px 6px;
+ -moz-border-radius: 6px 0 6px 6px;
+ border-radius: 6px 0 6px 6px;
+}
+
diff --git a/src/index.html b/src/index.html
index bbf3308..8dc203a 100644
--- a/src/index.html
+++ b/src/index.html
@@ -39,6 +39,15 @@
+
+ Context
+
+
+
+ Use variables as {var_name} in
+
+ - the request URL
+ - value of header
+ - part of the body
+ - value of querystring
+ - as username and password in BASIC authentication
+
+
+
+
+
+
+
diff --git a/src/js/app.js b/src/js/app.js
index 4a50134..516b904 100644
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -12,6 +12,13 @@ requirejs.config({
requirejs(['jquery','app/storage','knockout','knockout-secure-binding','hjls','app/request','app/bookmark','bootstrap'], function($,storage,ko,ksb,hjls,request,makeBookmarkProvider, bootstrap) {
+ function ContextVm(createDefault) {
+ const self = this;
+ this.name = ko.observable(createDefault ? 'default' : '');
+ this.variables = ko.observableArray();
+ this.isDefault = createDefault;
+ };
+
function RequestVm(request = {}) {
const self = this;
this.method = ko.observable('');
@@ -49,6 +56,8 @@ requirejs(['jquery','app/storage','knockout','knockout-secure-binding','hjls','a
function AppViewModel() {
const Resting = {
+ contexts : new ContextVm(true),
+
bookmarkSelected : new BookmarkSelectedVm(),
requestSelected : new RequestVm(),
responseContent : {},
@@ -92,13 +101,14 @@ requirejs(['jquery','app/storage','knockout','knockout-secure-binding','hjls','a
showBookmarkDeleteDialog: ko.observable(false),
showAboutDialog: ko.observable(false),
showCreditsDialog: ko.observable(false),
+ showContextDialog: ko.observable(false),
};
const bookmarkProvider = makeBookmarkProvider(storage);
- const convertToFormData = (data = []) =>
+ const convertToFormData = (data = [], context = {}) =>
data.filter(param => param.enabled()).reduce((acc, record) => {
- acc[record.name()] = record.value();
+ acc[record.name()] = _applyContext(record.value(),context);
return acc;
}, {});
@@ -114,6 +124,10 @@ requirejs(['jquery','app/storage','knockout','knockout-secure-binding','hjls','a
Resting.showCreditsDialog(true);
};
+ const contextDialog = () => {
+ Resting.showContextDialog(true);
+ };
+
const dismissCreditsDialog = () => {
Resting.showCreditsDialog(false);
};
@@ -122,8 +136,12 @@ requirejs(['jquery','app/storage','knockout','knockout-secure-binding','hjls','a
Resting.showAboutDialog(false);
};
- const convertToUrlEncoded = (data = []) =>
- data.filter(param => param.enabled()).map( param => `${param.name()}=${param.value()}`).join('&');
+ const dismissContextDialog = () => {
+ Resting.showContextDialog(false);
+ };
+
+ const convertToUrlEncoded = (data = [], context) =>
+ data.filter(param => param.enabled()).map( param => `${param.name()}=${_applyContext(param.value(),context)}`).join('&');
const updateBody = (bodyType, body) => {
clearRequestBody();
@@ -197,17 +215,20 @@ requirejs(['jquery','app/storage','knockout','knockout-secure-binding','hjls','a
}
};
- const dataToSend = () => {
+ const dataToSend = (context) => {
if (Resting.bodyType() === 'form-data') {
- return convertToFormData(Resting.formDataParams());
+ return convertToFormData(Resting.formDataParams(),context);
} else if (Resting.bodyType() === 'x-www-form-urlencoded') {
- return convertToUrlEncoded(Resting.formEncodedParams());
+ return convertToUrlEncoded(Resting.formEncodedParams(), context);
} else if (Resting.bodyType() === 'raw') {
- return Resting.rawBody().trim();
+ return _applyContext(Resting.rawBody().trim(),context);
}
};
- const _authentication = () => ({type: Resting.authenticationType(), username: Resting.username(), password: Resting.password()});
+ const _applyContextToArray = (a = [], context = {}) => {
+ return
+ };
+ const _authentication = (context = {}) => ({type: Resting.authenticationType(), username: _applyContext(Resting.username(),context), password: _applyContext(Resting.password(),context)});
const body = (bodyType) => {
if (bodyType === 'form-data') {
@@ -346,9 +367,9 @@ requirejs(['jquery','app/storage','knockout','knockout-secure-binding','hjls','a
}
};
- const convertToHeaderObj = headersList =>
+ const convertToHeaderObj = (headersList = [], context = {}) =>
headersList.filter(header => header.enabled()).reduce((acc, header) => {
- acc[header.name()] = header.value();
+ acc[header.name()] = _applyContext(header.value(),context);
return acc;
}, {});
@@ -367,16 +388,44 @@ requirejs(['jquery','app/storage','knockout','knockout-secure-binding','hjls','a
}
};
- const _convertToQueryString = (params = []) => {
- return params.filter(param => param.enabled()).map( param => ({name: param.name(), value: param.value()}));
+ const _convertToQueryString = (params = [], context = {}) => {
+ return params.filter(param => param.enabled()).map( param => ({name: param.name(), value: _applyContext(param.value(), context)}));
};
const send = () => {
+ const mapping = _mapContext();
if(Resting.requestSelected.url() && Resting.requestSelected.url().trim().length > 0) {
clearResponse();
- request.execute(Resting.requestSelected.method(),Resting.requestSelected.url(),convertToHeaderObj(Resting.requestHeaders()), _convertToQueryString(Resting.querystring()), Resting.bodyType(),Resting.dataToSend(),
- _authentication(),displayResponse);
+ const url = _applyContext(Resting.requestSelected.url(),mapping);
+ request.execute(Resting.requestSelected.method(),url,convertToHeaderObj(Resting.requestHeaders(), mapping), _convertToQueryString(Resting.querystring(), mapping), Resting.bodyType(),Resting.dataToSend(mapping),
+ _authentication(mapping),displayResponse);
+ }
+ };
+
+ const _mapContext = () => {
+ const mapping = {};
+ Resting.contexts.variables().filter(v => v.enabled()).forEach( v => mapping[v.name()] = v.value());
+ return mapping;
+ };
+
+ const _applyContext = (value = '',context = {}) => {
+ const tokens = _tokenize(value);
+ let computed = value.slice(0);
+ if(tokens) {
+ tokens.forEach(t => {
+ const contextVar = t.substring(1,t.length-1);
+ if(context[contextVar]) {
+ computed = computed.replace(t, context[contextVar]);
+ }
+ });
}
+ return computed;
+ };
+
+ const _tokenize = (v = '') => {
+ const varRegexp = /\{\w+\}/g;
+ const tokens = v.match(varRegexp);
+ return tokens;
};
const loadBookmarkInView = (bookmark = {}) => {
@@ -471,6 +520,19 @@ requirejs(['jquery','app/storage','knockout','knockout-secure-binding','hjls','a
}
};
+ const saveContext = () => {
+ storage.saveContext({name : Resting.contexts.name(), variables : _extractModelFromVM(Resting.contexts.variables()) });
+ dismissContextDialog();
+ };
+
+ const loadContexts = () => {
+ // load contexts
+ storage.loadContexts( ctx => {
+ Resting.contexts.variables(_convertToEntryItemVM(ctx.variables));
+ });
+ };
+
+
Resting.parseRequest = parseRequest;
Resting.dataToSend = dataToSend;
Resting.send = send;
@@ -491,8 +553,11 @@ requirejs(['jquery','app/storage','knockout','knockout-secure-binding','hjls','a
Resting.reset = reset;
Resting.aboutDialog = aboutDialog;
Resting.creditsDialog = creditsDialog;
+ Resting.contextDialog = contextDialog;
Resting.dismissCreditsDialog = dismissCreditsDialog;
Resting.dismissAboutDialog = dismissAboutDialog;
+ Resting.dismissContextDialog = dismissContextDialog;
+ Resting.saveContext = saveContext;
// FIXME: not good to expose this internal function
Resting._saveBookmark = _saveBookmark;
@@ -500,6 +565,8 @@ requirejs(['jquery','app/storage','knockout','knockout-secure-binding','hjls','a
Resting.loadBookmarkInView = loadBookmarkInView;
Resting.isBookmarkLoaded = isBookmarkLoaded;
Resting.bookmarkScreenName = bookmarkScreenName;
+ Resting.loadContexts = loadContexts;
+
return Resting;
}
@@ -531,6 +598,7 @@ requirejs(['jquery','app/storage','knockout','knockout-secure-binding','hjls','a
});
+
// Show all options, more restricted setup than the Knockout regular binding.
var options = {
attribute: "data-bind", // default "data-sbind"
@@ -541,6 +609,17 @@ requirejs(['jquery','app/storage','knockout','knockout-secure-binding','hjls','a
ko.bindingProvider.instance = new ksb(options);
- ko.applyBindings(new AppViewModel());
+ const appVM = new AppViewModel();
+ ko.applyBindings(appVM);
+
+ $('ul.dropdown-menu [data-toggle=dropdown]').on('click', function(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ $(this).parent().siblings().removeClass('open');
+ $(this).parent().toggleClass('open');
+ });
+
+ appVM.loadContexts();
+
});
});
diff --git a/src/js/app/storage.js b/src/js/app/storage.js
index 1b5ee94..4037762 100644
--- a/src/js/app/storage.js
+++ b/src/js/app/storage.js
@@ -4,11 +4,16 @@ define(['localforage'],function(localforage){
name: 'resting',
storeName: 'bookmarks',
});
-
+
+ const _contextsStore = localforage.createInstance({
+ name: 'resting',
+ storeName: 'contexts',
+ });
+
const deleteById = (id, callback) => {
localforage.removeItem(id, callback);
};
-
+
const save = (bookmark) => {
if(!bookmark.id) {
return { result: 'KO', message: 'id must be set'};
@@ -16,22 +21,35 @@ define(['localforage'],function(localforage){
localforage.setItem(bookmark.id, bookmark);
return { result: 'OK', message: ''};
};
-
+
const iterate = (callback, callbackResult) => {
if(!callbackResult) {
- localforage.iterate(function(value,key,iterationNumber) {
+ localforage.iterate(function(value,key,iterationNumber) {
callback(value);
});
} else {
- localforage.iterate(function(value,key,iterationNumber) {
+ localforage.iterate(function(value,key,iterationNumber) {
callback(value);
}, callbackResult);
};
};
-
+
+ const saveContext = (context) => {
+ _contextsStore.setItem(context.name, context);
+ return { result: 'OK', message: ''};
+ };
+
+ const loadContexts = (callback) => {
+ _contextsStore.iterate(function(value,key,iterationNumber) {
+ callback(value);
+ });
+ };
+
return {
save : save,
deleteById : deleteById,
iterate : iterate,
+ saveContext,
+ loadContexts,
};
});
diff --git a/src/manifest.json b/src/manifest.json
index ebcdca2..658465c 100644
--- a/src/manifest.json
+++ b/src/manifest.json
@@ -2,7 +2,7 @@
"description": "rest client focused on organization of the saved requests",
"manifest_version": 2,
"name": "Resting",
- "version": "0.7.3",
+ "version": "0.8.0",
"author" : "Mirko Perillo",
"homepage_url": "https://github.com/mirkoperillo/resting",
"icons": {