November 21, 2009

working with dijit.form.Form

i've found myself using this kind of pattern lately and found that i've also been explaining it quite often as well. so to avoid any further explanations here's some of what i've learned about how to try and design your code to leverage what dijit.form.Form (and other widgets) can provide. NOTE: my examples are based on dojo 1.3.2 and there are small variations to be made if you use dojo 1.4 (which is almost ready for release). the principles remain the same.

i'm going to cover:

  • how to set and get values for forms and form widgets

  • creating widgets to help with using complex data

  • widget templating including dojoAttachPoint and dojoAttachEvent

  • and more...



dijit setters and getters


dojo's widget library (dijit) provides a uniform way to set and get properties of widgets. this is provided by the dijit._Widget base class and it used like so:

var widget = new SomeWidget();

// get the value property of the widget
widget.attr('value');

// set the value property of the widget
widget.attr('value', 'a new value');

the .attr function makes it convenient to be able to consistently set or get properties. there's basically 3 ways that it works. i'll explain 2 of them - the 3rd way is also very powerful and makes use of the attributeMap to map widget properties to node properties.

first, .attr looks to the widget for some specially named functions to help with setting/getting properties. the naming convention is that if you're working with property foo then you can define a function called _setFooAttr which will be called in response to widget.attr('foo', value); and you can define a function called _getFooAttr which will be called in response to widget.attr('foo');

the _setFooAttr function will be passed the 2nd parameter of widget.attr('foo', value); and _getFooAttr should return a value to represent the foo property.

the 2nd option that .attr will use, is it will directly update/read the property eg. widget.attr('foo', value) is the same as widget.foo = value; and var value = widget.attr('foo'); is the same as var value = widget.foo;

just remember that if you provide the custom function to set/get then you will need to update the object property directly if you need it updated. widget.attr('foo', value); won't update widget.foo if you have _setFooAttr defined as a function in your widget. also, if you provide either of the custom functions, you are not required to provide the other one. so, for example, you might provide the setter function but not the getter function. in this case, the setter function will be used to set and the object property will be used for get.

ok, so knowing this, we can keep this in mind when we're creating widgets and leverage it as needed.

how dijit.form.Form uses setters and getters


now that we know about .attr we can look into how it's used for setting/getting values for dijit.form.Form. the value for a form is generated from the elements contained in that form. for dijit.form.Form it looks for all the widgets that are the descendants of it's containerNode and if that widget has a name property then the form value will have a property name that matches the value of the name of the widget. so, for example, if the widget's name is foo then the form's value will contain a foo property. the value of this property is obtained by getting the widget's value via widget.attr('value');

setting a dijit.form.Form value is simply the reverse of this. the form looks to the value being set and for the properties of that value, it looks for a child widget with a name to match that property and then tries to set that widget's value via widget.attr('value', formValue.widgetName); - this explanation does not follow the code exactly but the end result is the same. you should read the code if you want exact details :)

so, now that we know this, it's easy to see how if we have a dijit.form.TextBox with the name property set to firstName inside a form then we would expect the following

var form = dijit.byId('aForm'); // get a handle to the form

var value = form.attr('value'); // value.firstName will be the value of the dijit.form.TextBox

form.attr('value', {firstName: 'Bob'}); // the dijit.form.TextBox will now have the value 'Bob'


well this is easy for forms where we have a fixed number of fields and our data is 'flat' - ie, each widget just returns a string, a number, a boolean or even a Date.

what do we do though if we want to do something a little more complex? for example, we might have a form where a user can enter their name (first, last) and their address (street, city, state, zip, country). we could have a form where we have fields with names to match each of these but what if we wanted to treat the address as an object so that at the top-level, our form only has 3 properties (first, last, address)? well, we would need to have 3 widgets that have these names - first, last, address. ok, that's easy but dijit doesn't have an address widget! so... now it's time for us to write our own widget.

based on what we know about .attr and dijit.form.Form here's what our widget will have to do:

  • widget.attr('value'); will have to return an object that has properties called street, city, state, zip, country

  • widget.attr('value', formValue.address); will need to take an object with the above properties and display them appropriately

  • and our widget needs to have a name.



so, it looks like we will need to know how to build our own widgets to do this. fortunately, if you know enough about dojo to have been able to build a form with fields for street, city, state, zip and country then you're just a step or 2 away from being able to break out those fields into a widget.

well, every good tutorial has sample code, right? and i'm going to follow that trend but my code relates to a slightly more complex problem. once we work through the sample, then you should be able to come back and write the code for the problem described above.

the sample code will build a form that will allow a user to enter a schedule. so, they can choose the date for their schedule and then add tasks/events for that date. each of these tasks, will have a time and a description. the extra level of complexity that we have from the first problem (described above) is that we are going to allow the user to add or remove tasks which will mean we have a dynamic number of fields in our form. keep in mind, this problem was contrived just to demonstrate the principles in the code - it will be lacking in many ways but it serves the purpose of demonstrating the principles i'm trying to explain.

our high level design


basically, our design is driven by our data format. at the top-level, our data has 2 properties:

  • date - the date of our schedule

  • tasks - the list of tasks we have scheduled for this day



our list of tasks will be an array of objects. each of these objects will have the following properties:

  • time - the time the task begins

  • description - a description of the task



from this, we see that we need a widget for selecting a date (we'll choose dijit._Calendar) and we need a widget that can manage the list of tasks. since we can add and remove tasks from this list, then it seems like it would also be convenient to build a widget to represent a single task. this task widget will then need a widget for selecting a time (dijit.form.TimeTextBox) and another widget to enter a description (dijit.form.TextBox).

ok, so now that we have a high level design, we can start getting into the details of building these widgets. let's start with the widget for a single task and then work our way back up to the top-level of the form. you can look at the finished example if you want to see what we're working towards.

my.widgets.Task


this task widget will then need a widget for selecting a time (dijit.form.TimeTextBox) and another widget to enter a description (dijit.form.TextBox).


to keep this simple, we're going to leverage dijit's templating class - dijit._Templated. this allows us to define the high level DOM structure of our widget using html. based on our design, we can see that this should be fairly simple. the template for our Task widget is simply:

<div class='task'>
<div dojoType='dijit.form.TimeTextBox' dojoAttachPoint='timeInput'></div>
<div dojoType='dijit.form.TextBox' dojoAttachPoint='descInput'></div>
</div>


this is really simple. we have the outer div which becomes the domNode of our widget. we have put a class on this node so that we can easily style any part of this widget by including that class in our selector if necessary. this outer node has 2 children - 1 for our time widget and 1 for our description. using dojoType we can specify which widgets we want to have at the respective places in the DOM. in our javascript file that defines our widget, we just need to remember to set the widgetsInTemplate property to true so that our template gets parsed and those nodes get turned into widgets. we have also added dojoAttachPoints to our template. this allows us to map those widgets (in our template) to properties of our Task widget. by doing this, we can easily reference those individual widgets through those properties and one thing this will do is allow us to easily set or get the values of those widgets. so far, so good!

next we'll look at the javascript file which goes along with this template:

dojo.provide('my.widgets.Task');

dojo.require('dijit._Widget');
dojo.require('dijit._Templated');
dojo.require('dijit.form.TimeTextBox');
dojo.require('dijit.form.TextBox');

dojo.declare('my.widgets.Task', [dijit._Widget, dijit._Templated], {
templatePath: dojo.moduleUrl('my.widgets.templates', 'Task.html'),
widgetsInTemplate: true,

_setValueAttr: function(value){
if(value){
this.timeInput.attr('value', value.time || null);
this.descInput.attr('value', value.description || '');
} else {
this.timeInput.attr('value', null);
this.descInput.attr('value', '');
}
},

_getValueAttr: function(){
return {
time: this.timeInput.attr('value'),
description: this.descInput.attr('value')
};
}
});

ok - that's a little more code than the last block so let's break it down.

the first line is used by dojo's packaging system. this line identifies that this file provides the code for the 'my.widget.Task' package. the next few lines also relate to dojo's packaging system. they are just specifying the dependencies of this 'class'. the real meat of what we're looking at is inside dojo.declare. you can see that we are extending dijit._Widget and dijit._Templated. if you need more details about what's happening here then i'll leave it up to you to find out about this. the simple explanation here is that these are the 2 classes you most typically use to build a widget based on a template.

the next 2 lines define the path to our template and also let the templating system know that we have widgets in our template so it needs to parse the template to generate those widgets.

that leaves the 2 functions. if you can remember all the way back to the start, you'll know that these 2 functions are the custom functions used as setter/getter.

the setter checks if a parameter is passed in and sets the values of the time (this.timeInput) and description (this.descInput) widgets based on the value passed in. you can see how we're making use of the dojoAttachPoints that we indicated in our template and we're also using .attr to set the value of those widgets. this is the beauty of a standardized interface.

the getter simply forms an object with property names we're expecting (time, description) and assigns values to the properties based on the .attr getter for each of the widgets.

now that we have our Task widget, we can use it to build a list of tasks.

my.widgets.TaskList


ok, we're building on what we've learned already - no point in stopping here, we're going all the way :) i'll try to keep you a little entertained amidst all this learning.

...we need a widget that can manage the list of tasks.


this widget will manage our list of tasks - it will provide ways to:

  • add a task

  • remove a task

  • set the value from a list of tasks

  • return the list of tasks represented by this widget



add a task


we'll include a single button that will add another task with the current time and a default description of 'New Task'

remove a task


for each task that we add, we'll add a button which will provide a way for the user to remove that task

set the value from a list of tasks & return the list of tasks represented by this widget


for this, we'll write a custom setter and a custom getter - _setValueAttr and _getValueAttr

dijit._Container


since this widget will be a container for other widgets, we will mixin dijit._Container which will provide us with some functions to help managing adding/removing other widgets under our control. the only thing we really need to consider for this is that we need to designate a node as our containerNode so that these helpful functions will work :)

with all of this in mind, here's the template for this widget:

<div class='taskList'>
<div dojoAttachPoint='containerNode'></div>
<div class='add'>
<div dojoType='dijit.form.Button' dojoAttachEvent='onClick:newTask' iconClass='plusIcon'>Add Task</div>
</div>
</div>


you can see, we've got some similarities to the previous template but we also have some new features we didn't see before. i'm going to concentrate on the differences. you can see that we defined the containerNode we needed for dijit._Container using dojoAttachPoint. this is where our Task widgets will be added to when we add them.

the main feature we've used here that we didn't use previously is dojoAttachEvent. this provides a convenient way for us to connect the function of a widget in our template to a function defined in our TaskList class. the way it works, is that the string before the ':' is the name of the button's function that will be connected to our widgets function - the name on the right of the ':'. the connection is made by using the widget's connect method and this is just a convenient way to shortcut the code to do that. you can see from our template that when the onClick of the button is called, we will execute the newTask function of our widget.

now let's look at the javascript code so we can see what it looks like

dojo.provide('my.widgets.TaskList');

dojo.require('dijit._Widget');
dojo.require('dijit._Container');
dojo.require('dijit._Templated');
dojo.require('dijit.form.Button');
dojo.require('my.widgets.Task');

dojo.declare('my.widgets.TaskList', [dijit._Widget, dijit._Templated, dijit._Container], {
templatePath: dojo.moduleUrl('my.widgets.templates', 'TaskList.html'),
widgetsInTemplate: true,

name:'',
_multiValue: true,

postCreate: function(){
if(!this.getChildren().length){
// add a default 'blank' task
this.newTask();
}
},

// this creates a 'blank' new task - defaults the time to now and the description is 'New Task'
newTask: function(){
this.addTask({
time: new Date(),
description: 'New Task'
});
},

// adds a single task to our list of tasks
addTask: function(task){
new my.widgets.Task({
value: task
}).placeAt(this);
},

// sets our value - takes an array of objects
// each object should have a time and a description property
_setValueAttr: function(value){
// if we already have a value then we need to clear the widgets that represent that value
// removes all the widgets inside our 'containerNode'
this.destroyDescendants();
if(dojo.isArray(value) && value.length){
dojo.forEach(value, this.addTask, this);
} else {
// default to a 'blank' task
this.newTask();
}
},

_getValueAttr: function(){
// this will return an array of the values returned from our children via child.attr('value')
return dojo.map(this.getChildren(), "return item.attr('value');");
},

addChild: function(child){
this.inherited(arguments);
var button = new dijit.form.Button({
label: 'Remove Task',
showLabel: false,
iconClass: 'dijitEditorIcon dijitEditorIconCancel',
onClick: dojo.hitch(this, function(){
this.removeChild(child);
button.destroy();
})
}).placeAt(child.domNode);
},

removeChild: function(){
this.inherited(arguments);
if(!this.getChildren().length){
this.newTask();
}
}
});


these blocks of code are getting bigger and bigger but that's ok. we've only added a few new concepts. most of it is familiar already. let's jump right past what's already familiar and take a look at the new properties we're adding to this widget (name, _multiValue). the name property needs to be defined so that it can be picked up by the parser when we add this widget to our form. properties defined in markup will only be added by the parser to the widget if the properties are in the prototype of the widget. so we have to define name so we can use it later. also, _multiValue is a property we need to set as true whenever we will be setting/getting an array as the value of this widget.

i'm just going to summarize quickly what each of the functions do:

postCreate


just adds an empty task if no value was passed in when the widget was created

newTask


this is the function called when the user clicks the button to add a task.

addTask


this function is used to add another task to our list.

_setValueAttr


removes any previous tasks, adds each task in the list, or adds a 'blank' task if the new list is empty

_getValueAttr


returns an array which holds the results of calling the .attr('value'); getter for each child.

addChild


we're extending this function to add a button to each of our children so that when the button is clicked, the child is removed and the button is also destroyed.

removeChild


we're extending this function to add a 'blank' task to the list if we remove the last child.

ok - take some time and digest that... we have 1 final piece and that's the form. this is what we've been trying to achieve from the start... and it's taken quite a while to get here. you can see why i didn't want to keep explaining this any more!

my.widgets.App


at the top-level, our data has 2 properties:

  • date - the date of our schedule

  • tasks - the list of tasks we have scheduled for this day



it's a long way to the top if you want to rock n roll but we've finally made it! well, you already know what we're trying to achieve so here's the code:

<div class='myApp'>
<form dojoType='dijit.form.Form' dojoAttachPoint='form'>
<h1>Daily Schedule</h1>
<input dojoType='dijit._Calendar' dojoAttachEvent='onChange:onDateChange' name='date' class='dijitInline' />
<input dojoType='my.widgets.TaskList' name='tasks' class='dijitInline'/>
</form>
</div>

this time, our template contains a form which contains a heading and 2 elements. we really haven't introduced any new concepts here so since this post is already a mammoth i'll leave it for you to read it and comprehend it.

and fortunately, most of the hard work has already been done so the javascript is just shown here to complete the code:

dojo.provide('my.widgets.App');

dojo.require('dijit.form.Form');
dojo.require('dijit._Calendar');
dojo.require('dijit.form.TextBox');
dojo.require('my.widgets.TaskList');

// a little hack to get dijit._Calendar to work with the parser.
// dijit.form.Form only takes values from widgets with names and
// the parser only sees properties in the markup which already
// exist in the prototype - hence...
dojo.extend(dijit._Calendar, {
name: ''
});

dojo.declare('my.widgets.App', [dijit._Widget, dijit._Templated], {
templatePath: dojo.moduleUrl('my.widgets.templates', 'App.html'),
widgetsInTemplate: true,

// this is just going to be something to connect to so we can know when the user selects a new date
onDateChange: function(value){}
});


there's probably only 2 things which need explaining here. the dojo.extend and the empty onDateChange function.

the dojo.extend is needed so that we can give the calendar a name property in the template. if you remember i said earlier:
properties defined in markup will only be added by the parser to the widget if the properties are in the prototype of the widget.

it turns out that dijit._Calendar doesn't have the name property defined because it wasn't intended to be used like this (until dojo 1.4).

and the onDateChange function is here so that we can connect to this function and do something based on when the user selects a different date. since this post is so long... that's another post for another day :)

hopefully i've helped you get inside the 'brain of dojo' and now you can see some of the patterns that have been designed into dojo and you can leverage these patterns to work with dojo rather than fight against it or re-invent the wheel.

thanks for staying through to the end.

May 14, 2009

fetching everything selected in dojox.grid.DataGrid

the problem
the dojox.grid.DataGrid can page data and use virtual scrolling so that you can display a grid that can potentially display any of the data without needing to load all of it. the problem that we face with this is that if a user selects a row at the top of your data, quickly scrolls down towards the end of the data, holds shift and selects another row, then all the rows in between will be selected. this is fine - in fact this is desirable. however, since not all the rows in between were displayed then some of the data may not have been loaded. that is the problem. when we call
grid.selection.getSelected()
then it will return an array of items that might have some null items - these were the items that were not yet fetched.

the workaround
is an asynchronous getSelected() that takes a callback as a parameter. a warning should be given that this workaround is not thorough and maybe it should be called a hack! for me it's a proof of concept and would be a "nice to have" if dojo can provide something like this. the steps involved are straight forward
  • get the selected items in a similar manner to how it already happens with getSelected()
  • while getting those results, build an array of rows that need to be loaded
  • walk the array of unloaded items and fetch the missing pages of data
  • as each page is fetched, push the items into the original results
  • when we have all the pages, call the callback with the complete result set


here's the code pulled from a modified version of dojox.grid.DataSelection:
getSelectedAsync: function(cBack){
var result=[];
var load=[];
var requests=0;
for(var i=0, l=this.selected.length; i<l; i++){
if(this.selected[i]){
var item = this.grid.getItem(i);
item ? result.push(item) : load[i]=true;
}
}

for(var j=0, m=load.length; j<m; j++){
if(load[j]){
var pageIndex = this.grid._rowToPage(j);

requests++;
var fetchDfd = dojo.connect(this.grid, "_onFetchComplete", function(items){
dojo.disconnect(fetchDfd);
// for some reason we get called too many times...
if(requests--){
dojo.forEach(items, function(i){
result.push(i);
});
}
if(requests == 0){
cBack(result);
}
});

// request the missing page
this.grid._requestPag(pageIndex);
// skip the other items in this page so we don't have extra requests
j = this.grid._pageToRow(pageIndex+1); // -1 maybe? to account for j++ above
}
}
}

the warnings
  • this will defeat part of the purpose of the grid. the grid tries to only load pages on an "as needed" basis so that you don't have to load all of the data at one time. this goes against that idea.
  • for some reason i'm getting extra callbacks from the connect to "_onFetchComplete" and until i take the time to track them down, i would suggest that you use this at your own risk ;)
  • i've used a number of "private" functions to achieve this. that's both good and (potentially) bad. private functions are not promised to be unchanged from one release to the next but using them means that i don't have to repeat a lot of the work that is being done in these functions
i would certainly like to hear any comments about improvements to this and given a little time to live with it, i may at least search out the possibility of having something like this added to dojox.grid.DataSelection since after all DataSelection is for use with stores that implement the dojo.data api and many of them are largely asynchronous by nature.

February 7, 2009

inspiration

if you're involved in open source at all and haven't seen this, you should - especially if you're just hanging around the edges and not comfortable with diving right in. this was a great inspiration to me to get involved.

February 1, 2009

dereferencing object properties in dojox.grid.DataGrid

i'm working on a demo for Persevere and the interface is basically editable tables that represent the data that is persisted by Persevere. the editable tables are the dojox.grid.DataGrid and the demo is a primitive calendar application.

Persevere supports lazy loading and so if you have an Event object that has a calendar property to indicate which Calendar the event belongs in, that calendar is stored as a reference in the event. similarly, if the calendar has an owner that is a User object, then the owner property will reference a User. these objects that are stored as references have to be loaded before they can be referenced.

in my grid that displays the list of events, i want my columns to show:
  • Calendar - the name of the calendar that the event belongs to
  • Date - the date of the event
  • Event Name - the name/title of the event
  • Description - some extra details about the event


the way i have my Event class, this would mean that the ideal layout for my grid would be something like this:
var layout = [
{width: "20%", name: "Calendar", field: "calendar.name"},
{width: "15%", editable: true, field: "date", name: "Date", type: dojox.grid.cells.DateTextBox},
{width: "30%", editable: true, field: "name", name: "Event Name"},
{width: "35%", editable: true, field: "description", name: "Description"}
];

unfortunately, the grid (or the store - depending on where you want to post the blame) doesn't support drilling down into the item by using "calendar.name" and so the first iteration of our solution is to use a custom get function.
var getCalendar = function(row, item){
if(!item){
return this.defaultValue;
}
var store = this.grid.store;
var isItem = store && store.isItem(item, true);
if(isItem){
var calendar = store.getValue(item, "calendar");
var name = store.getValue(calendar, "name");
return name;
}
return this.defaultValue;
}
var layout = [
{width: "20%", name: "Calendar", get: getCalendar},
{width: "15%", editable: true, field: "date", name: "Date", type: dojox.grid.cells.DateTextBox},
{width: "30%", editable: true, field: "name", name: "Event Name"},
{width: "35%", editable: true, field: "description", name: "Description"}
];

this is fine for this one column but what about if this grid had more columns that i wanted to do this to and then what if i had more grids with more columns like this... that's a lot of repeating and we don't like repeating. so, the final iteration of our solution would ultimately be a subclass of dojox.grid.DataGrid that would allow us to use field names like "calendar.name"

i haven't taken this all the way through to a subclass yet but i have gone as far as overriding the default get method in the grid for each instance that i use this in.

WARNING: what i'm about to show here is only valid for reading and extra work probably needs to be done if you want to be able to edit these fields and have them write back to the store properly. if i have the need for it then i'll work on the code for writing back to the store and at that point i feel it would be worth a complete subclass and possibly a request to a dojo committer to include this as part of the grid.

var layout = [
{width: "20%", name: "Calendar", field: "calendar.name"},
{width: "15%", editable: true, field: "date", name: "Date", type: dojox.grid.cells.DateTextBox},
{width: "30%", editable: true, field: "name", name: "Event Name"},
{width: "35%", editable: true, field: "description", name: "Description"}
];
var grid = new dojox.grid.DataGrid({
structure: layout,
region: 'center',
store: this.store,
get: function(rowIndex, inItem){
var grid = this.grid;
var dereference = function(item, field){
var props = field.concat().split('.');
var val = item;
dojo.forEach(props, function(prop){
val = grid.store.getValue(val, prop);
});
return val;
}
return (!inItem ? this.defaultValue : (!this.field ? this.value : dereference(inItem, this.field)));
}
}, dojo.doc.createElement('div')).placeAt(this);


this code is taken from a custom widget that is a subclass of dijit.layout.BorderContainer that i'm using so take that into consideration when looking at lines like the last line of code that places the grid at this.

something worth noting is that inside the get function, the context is a cell.

to conclude, i'm certainly looking for any improvements to this and help with writing back to the store properly would be appreciated too. ultimately, i would like this functionality to be included in the grid.

January 15, 2009

dojo.data.ItemFileWriteStore

when starting to use the dojo data API, the first store that most people learn to use is the dojo.data.ItemFileReadStore (read only) and it's counterpart dojo.data.ItemFileWriteStore (read & write). out of the box, dojo.data.ItemFileWriteStore (IFWS) doesn't actually have a mechanism to write anything anywhere. everything is in place to write, but the functions that actually do the writing are essentially a no-op and so you have to write your own. this is something that is not clearly obvious when you start to use IFWS and so it needs to be clearly stated. also, here is a simple example of writing to your server URL. NOTE: IFWS is not a very efficient store since ALL the data is written each time the store is saved.

the complete example is available here

here's the javascript:

dojo.require("dojox.grid.DataGrid");
dojo.require("dojo.data.ItemFileWriteStore");
dojo.require("dijit.form.Button");

var fillStore = function(){
dojo.xhrGet({
url: "samfuqua.json",
handleAs: "json",
load: function(data, ioArgs){
dijit.byId('table').setStore(new dojo.data.ItemFileWriteStore({
data: data
}));
dijit.byId('table').store._saveEverything = function(saveCompleteCallback, saveFailedCallback, newFileContentString){
console.log(newFileContentString);
dojo.xhrPost({
url: "yourURL",
postData: dojo.toJson(dojo.fromJson(newFileContentString)), // this unprettifies the string
error: saveFailedCallback,
load: saveCompleteCallback
});
}
}
});
}

dojo.addOnLoad(fillStore);


and here's the html:

<body class="tundra">
<button dojoType="dijit.form.Button" onclick="dijit.byId('table').store.revert();">Revert</button>
<button dojoType="dijit.form.Button" onclick="dijit.byId('table').store.save();">Save</button>
<table id="table" dojoType="dojox.grid.DataGrid">
<thead>
<tr>
<th field="genre">Genre</th>
<th field="author" editable=true>Author</th>
<th field="title">Title</th>
<th field="price">Price</th>
</tr>
</thead>
</table>
</body>


the part to focus in on is this section here:

dijit.byId('table').store._saveEverything = function(saveCompleteCallback, saveFailedCallback, newFileContentString){
console.log(newFileContentString);
dojo.xhrPost({
url: "yourURL",
postData: dojo.toJson(dojo.fromJson(newFileContentString)), // this unprettifies the string
error: saveFailedCallback,
load: saveCompleteCallback
});
}


for IFWS to actually do something, you have to override either the _saveEverything or the _saveCustom functions. in this case, we are overriding _saveEverything. when IFWS calls the _saveEverything function it will pass a saveCompleteCallback, saveFailedCallback and a newFileContentString. conveniently, these are basically what we would need to have to make an xhrPost to our server :)

the xhrPost is fairly straightforward, you provide a url, some data, an error callback and a load (success) callback. the only manipulation we've had to do here is "unprettify" the newFileContentString. when newFileContentString is passed in, it is a "prettified" json string - ie it has tabs, and newlines, etc. to format the string so that it looks "pretty" when it's printed. this is nice if you're writing the text directly to a file but this can cause problems if your server needs to understand the data so to unprettify it, i've converted the json to an object and then back to json again without prettifying it.

this is just a quick look at IFWS and for a more complete look at it, take a look at the docs at dojocampus