Backbone. Form with file upload, how to handle? - backbone.js

I want to organize the workflow only through the REST API. I have a form that allows to upload image (enctype="multipart/form-data"). How do I handle this form via backbone? Help me please, how I can to serialize it into JSON with a file field.
Thanks.
Vitaliy

If you are using HTML5, you can use the readAsDataURL method from the file api to read and store it on your models.
Here's the code i use to read and store.
var Image = Backbone.Model.extend({
readFile: function(file) {
var reader = new FileReader();
// closure to capture the file information.
reader.onload = (function(theFile,that) {
return function(e) {
//set model
that.set({filename: theFile.name, data: e.target.result});
};
})(file,this);
// Read in the image file as a data URL.
reader.readAsDataURL(file);
}
});

You could try the jquery.iframe.transport plugin.

IMHO, you cannot serialize a file into JSON.
If you need to send some data along with the file you can send them as query params with POST method.
www.example.com/upload?param1=value1&param2=value2

There's no good way to submit a file via AJAX. So I wrote a function to fake it--it inserts a secret iframe into your DOM that is never visible but still works as a target to submit your form on, and it installs a function for your response to call that cleans house when the file is uploaded.
Have your upload form's submit button fire this function I wrote. It uses jQuery because it's easy and nice, but in principle that shouldn't be strictly necessary:
function backgroundUpload(form, container) {
$(container).append('<iframe name="targetFrame" id="targetFrame" style="display: none; height: 0px; width:0px;" ></iframe>');
$(form).attr('target', 'targetFrame');
window.backgroundUploadComplete = function() {
//clear your form:
$(form).find(':file').val('');
$(form).find(':text').val('');
//do whatever you do to reload your screenful of data (I'm in Backbone.js, so:)
window.Docs.fetch().complete( function() { populateDocs(); });
//get rid of the target iframe
$('#targetFrame').remove();
};
$(form).submit();
}
Then have your form handler that does your file parsing and saving return the string:
<script>window.parent.backgroundUploadComplete();</script>
Your form can look like:
<form id="uploadForm" method="POST" action="/your/form/processor/url" enctype="multipart/form-data">
<input type="file" name="file"/>
<!-- and other fields as needed -->
<input type="button" onClick="backgroundUpload(this.form, $('#documents'));" value="Upload" />
</form>
(#documents is the div that this form lives in. Could probably be any DOM element, it just needs a home.)

events : {
"click #uploadDocument" : "showUploadDocumentDetails",
"change #documents" : "documentsSelected",
"click .cancel-document" : "cancelDocument"
},
showUploadDocumentDetails : function(event) {
$('#id-gen-form').attr("enctype","multipart/form-data");
$('#id-gen-form').attr("action",this.model.url);
var config = {
support : "image/jpg,image/png,image/bmp,image/jpeg,image/gif", // Valid file formats
form: "id-gen-form", // Form ID
dragArea: "dragAndDropFiles", // Upload Area ID
uploadUrl: this.model.url // Server side upload url
};
initMultiUploader(config);
if($('#uploadDocument').attr("checked")){
$('#id-documentCategory-div').show();
$('#id-documentName-div').show();
this.model.set({"uploadDocument": "YES"},{silent: true});
}
else{
$('#id-documentCategory-div').hide();
$('#id-documentName-div').hide();
this.model.set({"uploadDocument": "NO"},{silent: true});
}
},
cancelDocument : function(event) {
var targ;
if (!event) event = window.event;
if (event.target) targ = event.target;
else if (event.srcElement) targ = event.srcElement;
$('#' + event.target.id).parent().parent().remove();
var documentDetails = this.model.get("documentDetails");
documentDetails = _.without(documentDetails, _(documentDetails).find(function(x) {return x.seqNum == event.target.id;}));
this.model.set({
"documentDetails" : documentDetails
}, {
silent : true
});
},
documentsSelected : function(event) {
/*var targ;
if (!event) event = window.event;
if (event.target) targ = event.target;
else if (event.srcElement) targ = event.srcElement;
if (targ.nodeType == 3) // defeat Safari bug
targ = targ.parentNode;
var files = event.target.files; // FileList object
var html = [];
var documentDetails = [];
$(".files").html(html.join(''));
var _this = this;
_this.model.set({
"documentDetails" : documentDetails
}, {
silent : true
});
var seqNum = 0;
for(var i=0; i< files.length; i++){
(function(file) {
html.push("<tr class='template-upload' style='font-size: 10px;'>");
html.push("<td class='name'><span>"+file.name+"</span></td>");
html.push("<td class='size'><span>"+file.size+" KB <br/>"+file.type+"</span></td>");
//html.push("<td><div class='progress progress-success progress-striped active'style='width: 100px;' role='progressbar' aria-valuemin='0' aria-valuemax='100' aria-valuenow='0'><div class='bar' style='width:0%;'></div></div></td>");
if(LNS.MyesqNG.isMimeTypeSupported(file.type)){
if(!LNS.MyesqNG.isFileSizeExceeded(file.size)){
html.push("<td class='error' colspan='2'></td>");
var reader = new FileReader();
console.log(reader);
reader.onload = function(e) {
var targ;
if (!e) e = window.event;
if (e.target) targ = e.target;
else if (e.srcElement) targ = e.srcElement;
if (targ.nodeType == 3) // defeat Safari bug
targ = targ.parentNode;
console.log(e.target.result);
var content = e.target.result;
var document = new Object();
document.name = file.name;
document.type = file.type;
document.content = content;
document.seqNum = "document"+seqNum;
seqNum++;
documentDetails.push(document);
// _this.model.set({"documentDetails" : documentDetails},{silent:true});
};
reader.readAsDataURL(file, "UTF-8");
}else{
seqNum++;
html.push("<td class='error' colspan='2'><span class='label label-important'>Error</span> Too long</td>");
}
}else{
seqNum++;
html.push("<td class='error' colspan='2'><span class='label label-important'>Error</span> Not suported</td>");
}
html.push("<td><a id='document"+i+"' class='btn btn-warning btn-mini cancel-document'>Cancel</a></td>");
html.push("</tr>");
})(files[i]);
}
$(".files").html(html.join(''));*/
}
LNS.MyesqNG.isMimeTypeSupported = function(mimeType){
var mimeTypes = ['text/plain','application/zip','application/x-rar-compressed','application/pdf'];
if($.inArray(mimeType.toLowerCase(), mimeTypes) == -1) {
return false;
}else{
return true;
}
};
LNS.MyesqNG.isFileSizeExceeded = function(fileSize) {
var size = 2000000000000000000000000000;
if(Number(fileSize) > Number(size)){
return true;
}else{
return false;
}
};
Use this, it can work but not more than 5 MB file

Based on Anthony answer (https://stackoverflow.com/a/10916733/2750451), I've written a solution in coffeescript based on a defer object.
readFile: (file) =>
def = $.Deferred()
reader = new FileReader()
reader.onload = (ev) =>
def.resolve
name: file.name
binary: ev.target.result
reader.onerror = ->
def.reject()
reader.readAsDataURL(file)
def.promise()
Then, you could use it this way
readFile(file)
.done (parsedFile) =>
# do whatever you want with parsedFile
#model.set
image_name: parsedFile.name
image: parsedFile.binary
#model.save
.fail ->
console.log "readFile has failed"
To handle it on the server side (because it's Base64 encoded), here the solution in RoR (based on https://stackoverflow.com/a/16310953/2750451)
my_object.image = decode_image(params[:image])
my_object.image.name = params[:image_name]
def decode_image(encoded_file)
require 'base64'
image_data_string = split_base64(encoded_file)[:data]
Base64.decode64(image_data_string)
end
def split_base64(uri)
if uri.match(%r{^data:(.*?);(.*?),(.*)$})
return {
type: $1, # "image/png"
encoder: $2, # "base64"
data: $3, # data string
extension: $1.split('/')[1] # "png"
}
end
end

To ellaborate on Anthony Chua's answer. You need to add Image handling to Backbone.Form.editors like
Backbone.Form.editors.Image = Backbone.Form.editors.Text.extend({
tagName: 'div',
events: {
'change input[type=file]': 'uploadFile',
'click .remove': 'removeFile'
},
initialize: function(options) {
_.bindAll(this, 'filepickerSuccess', 'filepickerError', 'filepickerProgress');
Backbone.Form.editors.Text.prototype.initialize.call(this, options);
this.$input = $('<input type="hidden" name="'+this.key+'" />');
this.$uploadInput = $('<input type="file" name="'+this.key+'" accept="image/*" />');
this.$loader = $('<p class="upload-status"><span class="loader"></span> please wait..</p>');
this.$error = $('<p class="upload-error error">Error</p>');
this.$list = $('<ul class="file-list">');
},
// return an array of file dicts
getValue: function() {
var val = this.$input.val();
return (val ? JSON.parse(val) : [])[0].value;
},
setValue: function(value) {
var str, files = value;
if (_(value).isObject()) {
str = JSON.stringify(value);
} else {
files = value ? JSON.parse(value) : [];
}
this.$input.val(str);
this.updateList(files);
},
render: function(options) {
Backbone.Form.editors.Text.prototype.render.apply(this, arguments);
this.$el.append(this.$input);
this.$el.append(this.$uploadInput);
this.$el.append(this.$loader.hide());
this.$el.append(this.$error.hide());
this.$el.append(this.$list);
return this;
},
uploadFile: function() {
var fileInput = this.$uploadInput.get(0);
var fileObj = fileInput.files[0]
var reader = new FileReader();
var that = this;
// closure to capture the file information.
reader.onload = function(file){
var dataURL = reader.result;
var fileValue = {
value: dataURL,
name: fileObj.name,
content_type: fileObj.type
}
that.filepickerSuccess(fileValue);
};
// Read in the image file as a data URL.
reader.readAsDataURL(fileObj);
},
filepickerSuccess: function(files) {
console.log('File (raw)', files);
this.$loader.hide();
this.$error.hide();
this.$uploadInput.val('');
// when uploading one file, it returns just an object
if (!_(files).isArray()) { files = [files]; }
// turn response array into a flatter array of objects
var newFiles = _(files).map(function(file, index) {
return {
url: "#",
value: file.value,
filename: file.name,
key: index,
content_type: file.type
};
});
console.log('File (processed)', newFiles);
this.setValue(newFiles);
},
filepickerError: function(msg) {
console.debug('File error', msg);
this.$loader.hide();
this.$error.show();
},
filepickerProgress: function(percent) {
this.$loader.show();
this.$error.hide();
},
updateList: function(files) {
// this code is currently duplicated as a handlebar helper (I wanted to let this
// backbone-forms field stand on its own)
this.$list.empty();
_(files).each(function(file) {
var a = $('<a>', {
target: '_blank',
href: file.url,
text: file.filename + ' (' + file.content_type + ') '
});
var li = $('<li>').append(a);
li.append(a, ' ', $('<i class="icon-remove"></i>').data('key', file.key));
this.$list.append(li);
}, this);
this.$list[files.length ? 'show' : 'hide']();
},
removeFile: function(ev) {
if (ev) ev.preventDefault();
var files = this.getValue();
this.setValue([]);
}
});
You can use above code as follows
var ImgSlot = Backbone.Model.extend({
defaults: {
},
schema: {
imageField: {
type: "Image"
}
}
})
Render form using:
this.form = new Backbone.Form({
model: new ImgSlot(),
submitButton: "Example Image file input handling"
}).render();
var errors = that.form.commit({validate: true})
if(errors != null)
{
return false;
}
var data = that.form.model.attributes;
console.debug(data.imageField); // Will return base64 of image selected.

It is not possible to submit a file over AJAX before HTML5 (including IE9).
You need to sync the model attributes over ajax, and then send another html form post with the file, and then sync them up somehow. Generally, save the model over ajax, get an id back, add the id to the other form, and then post the file.
The jQuery plug in "jquery.form" may help you to construct a form to post the file. It manages the "hidden iframe trick" so that it looks like AJAX to the end user.
You might just need to spend some time googling "hidden iframe trick" ...

Related

RecordRTC Extension problematically save a div

Using RecordRTC as an extension and also in my development work. Great work!
Is there a way my site can programatically record a div only, instead of the whole tab?
var myformat = {enableTabCaptureAPI: true, enableSpeakers: true}
if(typeof RecordRTC_Extension === 'undefined') {
alert('RecordRTC chrome extension is either disabled or not installed.');
} else {
var recorder = new RecordRTC_Extension();
//recorder.startRecording(recorder.getSupoortedFormats()[4], function() {
recorder.startRecording(myformat, function() {
setTimeout(function() {
recorder.stopRecording(function(blob) {
console.log(blob.size, blob);
var url = URL.createObjectURL(blob);
invokeSaveAsDialog(blob);
video.src = url;
});
}, 3000);
});
}
You do not need a chrome extension to record a DIV. I'm copying here complete demo that can record a DIV.
Start/Stop buttons:
<button id="btn-start-recording">Start Recording</button>
<button id="btn-stop-recording" disabled>Stop Recording</button>
DIV to be recorded:
<div id="element-to-record">
<input value="type something">
</div>
Optionally a hidden CANVAS:
<canvas id="background-canvas" style="position:absolute; top:-99999999px; left:-9999999999px;"></canvas>
Hidden canvas is used to draw DIV and get webp images. It is till an optional step. You can either append it to he DOM or keep in the memory.
Link RecordRTC and HTML-2-Canvas:
<script src="https://cdn.WebRTC-Experiment.com/RecordRTC.js"></script>
<script src="https://cdn.webrtc-experiment.com/html2canvas.min.js"></script>
Complete javascript code:
var elementToRecord = document.getElementById('element-to-record');
var canvas2d = document.getElementById('background-canvas');
var context = canvas2d.getContext('2d');
canvas2d.width = elementToRecord.clientWidth;
canvas2d.height = elementToRecord.clientHeight;
var isRecordingStarted = false;
var isStoppedRecording = false;
(function looper() {
if(!isRecordingStarted) {
return setTimeout(looper, 500);
}
html2canvas(elementToRecord).then(function(canvas) {
context.clearRect(0, 0, canvas2d.width, canvas2d.height);
context.drawImage(canvas, 0, 0, canvas2d.width, canvas2d.height);
if(isStoppedRecording) {
return;
}
requestAnimationFrame(looper);
});
})();
var recorder = new RecordRTC(canvas2d, {
type: 'canvas'
});
document.getElementById('btn-start-recording').onclick = function() {
this.disabled = true;
isStoppedRecording =false;
isRecordingStarted = true;
recorder.startRecording();
document.getElementById('btn-stop-recording').disabled = false;
};
document.getElementById('btn-stop-recording').onclick = function() {
this.disabled = true;
recorder.stopRecording(function() {
isRecordingStarted = false;
isStoppedRecording = true;
var blob = recorder.getBlob();
// document.getElementById('preview-video').srcObject = null;
document.getElementById('preview-video').src = URL.createObjectURL(blob);
document.getElementById('preview-video').parentNode.style.display = 'block';
elementToRecord.style.display = 'none';
// window.open(URL.createObjectURL(blob));
});
};
ONLINE demo:
https://www.webrtc-experiment.com/RecordRTC/simple-demos/recording-html-element.html

Files upload testing in Enzyme

I have a FileInput in my render function
<FileInput
accept= "image/jpeg,image/png,audio/mp3"
onChange= {this.fileInputOnChange}
children= {<i className="fa fa-paperclip"/>}
className= 'fileInput'
/>
I need to write a test for file upload, when I simulate the change function it call the function fileInputOnChange
fileInputOnChange: function(evt){
var file = evt.target.files[0];
var fileReader = new FileReader();
fileReader.onload = function(readFile){
// Check the file type.
var fileType = file.type.toLowerCase();
if(fileType.lastIndexOf('image') === 0 && (fileType.lastIndexOf('png') >= 0 || fileType.lastIndexOf('jpeg'))){
var image = new Image();
image.onload = function(){
var attachedFile = {
attached: file,
mediaSource: readFile.target.result,
type: 'image'
}
this.props.onChange(attachedFile);
}.bind(this);
image.onerror = function(){
this.props.onError("INVALID_TYPE");
}.bind(this);
image.src = readFile.target.result;
}else if(fileType.lastIndexOf('audio') === 0 && fileType.lastIndexOf('mp3') >= 0){
//#todo: manage audio upload here
var audio = new Audio();
audio.oncanplaythrough = function(){
var attachedFile = {
attached: file,
mediaSource: readFile.target.result,
type: 'audio'
}
this.props.onChange(attachedFile);
}.bind(this);
audio.onerror = function(e){
this.props.onError("INVALID_TYPE");
}.bind(this)
audio.src = readFile.target.result;
}else if (fileType.lastIndexOf('video') === 0 && fileType.lastIndexOf('mp4') >= 0){
var video = document.createElement('video');
video.oncanplaythrough = function(){
var attachedFile = {
attached: file,
mediaSource: readFile.target.result,
type: 'video'
}
this.props.onChange(attachedFile);
}.bind(this);
video.onerror = function(){
this.props.onError("INVALID_TYPE");
}.bind(this)
video.src = readFile.target.result;
}
}.bind(this);
fileReader.onerror = function(){
this.props.onError("READING_ERROR");
}.bind(this)
fileReader.readAsDataURL(file); }
I could not add any files while simulating the upload button, I am confused of how to write the test for this scenario. Anyone ever came across this kinda scenarios? I would be greatful for all sorta helps.
it('testing attachfile change function...',()=>{
const wrapper=shallow(<AttachFile />);
wrapper.find('FileInput').simulate('change');
console.log(wrapper.debug());
});
When I tried the above test I got the following error
TypeError: Cannot read property 'target' of undefined
at [object Object].fileInputOnChange (js/components/home/chats/AttachFile.react.js:11:16)
at node_modules/enzyme/build/ShallowWrapper.js:768:23
at ReactDefaultBatchingStrategyTransaction.Mixin.perform (node_modules/react/lib/Transaction.js:138:20)
at Object.ReactDefaultBatchingStrategy.batchedUpdates (node_modules/react/lib/ReactDefaultBatchingStrategy.js:63:19)
at batchedUpdates (node_modules/react/lib/ReactUpdates.js:98:20)
at node_modules/enzyme/build/ShallowWrapper.js:767:45
at withSetStateAllowed (node_modules/enzyme/build/Utils.js:196:3)
at ShallowWrapper.simulate (node_modules/enzyme/build/ShallowWrapper.js:764:42)
at Context.<anonymous> (test/sample.js:40:27)
You'll need to provide a mocked event object, something like:
wrapper.find('FileInput').simulate('change', {
target: {
files: [
'dummyValue.something'
]
}
});
Your component its making a lot of work inside, its like a huge side effect (it defines two callbacks with logic nailed). It's going to be difficult, but I suppose you'll need to mock FileReader as well using two spies, one reacting to the readAsDataURL calling the onload and another one calling the onerror.
Then you can check if your callbacks are doing what is supposed to.
Hope it helps!

Uploading blob file to Amazon s3

I am using ngCropImage to crop an image and want to upload it following this link:
NgCropImage directive is returning me dataURI of the image and I am converting it to a blob (after converting it I get a blob object: which has size and type), Converted DataURI to blob using following code:
/*html*/
<img-crop image="myImage" result-image="myCroppedImage" result-image-size="250"></img-crop>
$scope.myImage='';
$scope.myCroppedImage = {image: ''}
var blob;
//called when user crops
var handleFileSelect=function(evt) {
var file=evt.currentTarget.files[0];
var reader = new FileReader();
reader.onload = function (evt) {
$scope.$apply(function($scope){
$scope.myImage=evt.target.result;
});
};
console.log($scope.myCroppedImage)
reader.readAsDataURL(file);
var link = document.createElement('link');
blob = dataURItoBlob($scope.myCroppedImage)
console.log(blob)
};
angular.element(document.querySelector('#fileInput')).on('change',handleFileSelect);
function dataURItoBlob(dataURI) {
// convert base64/URLEncoded data component to raw binary data held in a string
var binary = atob(dataURI.split(',')[1]);
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
var array = [];
for(var i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
return new Blob([new Uint8Array(array)], {type: mimeString});
}
$scope.upload = function(file) {
//var file = new File(file, "filename");
// Configure The S3 Object
console.log($scope.creds)
AWS.config.update({ accessKeyId: $.trim($scope.creds.access_key), secretAccessKey: $.trim($scope.creds.secret_key) });
AWS.config.region = 'us-east-1';
var bucket = new AWS.S3({ params: { Bucket: $.trim($scope.creds.bucket) } });
if(file) {
//file.name = 'abc';
var uniqueFileName = $scope.uniqueString() + '-' + file.name;
var params = { Key: file.name , ContentType: file.type, Body: file, ServerSideEncryption: 'AES256' };
bucket.putObject(params, function(err, data) {
if(err) {
// There Was An Error With Your S3 Config
alert(err.message);
return false;
}
else {
// Success!
alert('Upload Done');
}
})
.on('httpUploadProgress',function(progress) {
// Log Progress Information
console.log(Math.round(progress.loaded / progress.total * 100) + '% done');
});
}
else {
// No File Selected
alert('No File Selected');
}
}
$scope.uniqueString = function() {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < 8; i++ ) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
//for uploading
$scope.handleSave = function(){
$scope.upload(blob);
}
Now, I want to upload this blob on S3 using this, but I am not able to figure out how to upload this blob file to s3 (as I am not getting 'name' in the blob file)
Any help would be really appreciated. Thanks
You can always create file from blob. You can pass file name also.
var file = new File([blob], "filename");
This same file object you can use to upload on s3.
Change your handleSave method to following. File name will be abc.png for now
//for uploading
$scope.handleSave = function(){
blob = dataURItoBlob($scope.myCroppedImage)
$scope.upload(new File([blob], "abc.png"));
}
It is not advisable that you do to put the key
secretAccessKey: $.trim($scope.creds.secret_key)
on the client side ... That is not done !, anyone can manipulate your bucket at will.

How to show data stored in local storage in my table

I am creating a contact Manager using backbone.js,this is my code
$(document).ready(function() {
var Contact=Backbone.Model.extend({
defaults: {
fname : '',
lname : '',
phoneno : ''
}
});
var ContactList=Backbone.Collection.extend({
model : Contact,
localStorage: new Store("ContactList-backbone")
});
var ContactView=Backbone.View.extend({
el : $('div#contactmanager'),
events: {
'click #additems' : 'add'
},
initialize: function() {
this.render();
this.collection = new ContactList();
},
add : function() {
s1=$('#fname').val();
s2=$('#lname').val();
s3=$('#phoneno').val();
if(s1 =="" || s2=="" || s3=="")
{
alert("Enter values in Textfield");
}
else
{
$('#tlist').append("<tr><td>"+s1+"</td><td>"+s2+"</td><td>"+s3+"</td> </tr>");
cont=new Contact({fname:s1,lname:s2,phoneno:s3});
this.collection.add(cont);
cont.save();
}
},
render : function() {
$(this.el).append("<label><b>First Name</b></label><input id= 'fname' type='text' placeholder='Write ur first name'></input>");
$(this.el).append("<br><label><b>Last Name</b></label><input id= 'lname' type='text' placeholder='Write ur last name'></input>");
$(this.el).append("<br><label><b>Phone Number</b></label><input id= 'phoneno' type='text' placeholder='Write ur phone number'></input>");
$(this.el).append("<br><button id='additems'>ADD</button>");
var showdata=localStorage.getItem('ContactList-backbone',this.model);
console.log(showdata,"showdata");
}
return this;
},
});
var contactManager=new ContactView();
});
This is how I used localstorage
function S4() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
};
function guid() {
return (S4());
};
var Store = function(name)
{
this.name = name;
var store = localStorage.getItem(this.name);
this.data = (store && JSON.parse(store)) || {};
};
_.extend(Store.prototype,
{
save: function() {
localStorage.setItem(this.name, JSON.stringify(this.data));
},
create: function(model) {
if (!model.id) model.id = model.attributes.id = guid();
this.data[model.id] = model;
this.save();
return model;
},
Backbone.sync = function(method, model, options) {
var resp;
var store = model.localStorage || model.collection.localStorage;
switch (method) {
case "create": resp = store.create(model); break;
//I am using only create
}
if (resp) {
options.success(resp);
}
else {
options.error("Record not found");
}
};
The data is getting stored in local storage.
But I can't figure out how to show this data in my table when the page is reloded.
For eg: Iwant to show first name,lname and phone no in table ;
I am new to backbone so plz do help me
Basically you will want to bind the add event in your collection which gets will get called for each item that is being added to the collection and then in the function your binding it to add the code to add the rows to your table. Also you will want to remove the code that is in your current add method that adds the row since it will now be generated when the item gets added to your collection.
Using your code as a base something along the lines of
var ContactView=Backbone.View.extend({
el : $('div#contactmanager'),
events: {
'click #additems' : 'add'
},
initialize: function() {
this.render();
this.collection = new ContactList();
this.collection.bind('add', this.addContact, this);
},
addContact: function(contact) {
//this will get called when reading from local storage as well as when you just add a
//model to the collection
$('#table').append($('<tr><td>' + contect.get('name') + </td></tr>'));
}
Another point being that you have already have underscore.js on your page (since its a requirement for backbone.js) you may want to consider moving your code to generate html to a underscore.js template.
http://documentcloud.github.com/underscore/#template
since you're only using create, you're never going to hit read. Replace your switch statement with by adding a read method
switch (method)
{
case "read":
resp = model.id != undefined ? store.find(model) : store.findAll();
break;
case "create":
resp = store.create(model);
break;
}

Loading content dynamically (panels) in an Ext Js Viewport

Well basically im looking on this problem, i have many components with dinamic stuff that is written in the server side with PHP.
Depending on the user my components will change, based on the role of the user.
So i need to know any ways/examples/info on how to do this.
1- I used the load function EXTJS has, but it clearly says i wont load script only plain text.
2- i used eval() but im a bit scared o this approach, like this example crate layout component (static)
var contentPanel = new Ext.Panel({
frame: true,
style: {marginTop: '10px'},
height: 315,
border: true,
bodyBorder: false,
layout: 'fit',
id: 'contentPanel'
});
var mainPanel = new Ext.Panel({
title: 'Panel Principal',
id: 'mainPanel',
border: true,
frame: true,
width: '50%',
style: {margin: '50px auto 0 auto'},
height: 400,
renderTo: Ext.getBody(),
items: [
{
html: 'Panel 1'
},
{
html: 'Panel 2'
},
contentPanel
]
})
and update the content of the layout with js files written on the server
function receiveContent(options, success, response)
{
var respuesta = response.responseText;
//console.log(respuesta);
eval(respuesta);
//console.log(options.url);
url = options.url;
url = url.substring(0,(url.search(/(\.)/)));
var contenedor = Ext.getCmp('contentPanel');
contenedor.removeAll();
var contenido = Ext.getCmp(url);
contenedor.add(contenido);
contenedor.doLayout();
}
function requestContent(panel)
{
//panel es el nombre del archivo que quiero
Ext.Ajax.request({
url: panel+'.js',
callback: receiveContent
});
}
any other way for this to be done, what i DONT want to do is making a million different components and load them ALL at login time like many people seem to say
To address your questions:
The .load method WILL load script and evaluate it once the content has finished loading, however to accomplish this you will need to set the scripts:true option, an example may be:
my_panel.load({
url: 'url_to_load.php/hmt/html/asp...',
params: {param1: param1value, param2: param2value...etc},
nocache: true,
timeout: 30,
scripts: true
});
Using eval() is fine...but seeing as the scripts:true config option above accomplishes this for javascript in the source file, you shouldnt need to use this.
Hope this helps
You might load JavaScript dynamically using something like like below - there are a hundred variations on the web. In this way, you would avoid the AJAX call and handling the response (and subsequent eval).
var aHeadNode = document.getElementById('head')[0];
var aScript = document.createElement('script');
aScript.type = 'text/javascript';
aScript.src = "someFile.js";
aHeadNode.appendChild(oScript);
What I understood from your question is that, you are looking for dynamic JS file loader with a callback handler i.e. the callback function will be called only when the file is loaded fully. I also faced similar problems at start and after searching a lot and doing some research, I developed the following code, it provides absolute Dynamic JS and CSS file loading functionality :
Class ScriptLoader: (Put it in a separate file and load it at first)
ScriptLoader = function() {
this.timeout = 30;
this.scripts = [];
this.disableCaching = false;
};
ScriptLoader.prototype = {
processSuccess : function(response) {
this.scripts[response.argument.url] = true;
window.execScript ? window.execScript(response.responseText) : window
.eval(response.responseText);
if (response.argument.options.scripts.length == 0) {
}
if (typeof response.argument.callback == 'function') {
response.argument.callback.call(response.argument.scope);
}
},
processFailure : function(response) {
Ext.MessageBox.show({
title : 'Application Error',
msg : 'Script library could not be loaded.',
closable : false,
icon : Ext.MessageBox.ERROR,
minWidth : 200
});
setTimeout(function() {
Ext.MessageBox.hide();
}, 3000);
},
load : function(url, callback) {
var cfg, callerScope;
if (typeof url == 'object') { // must be config object
cfg = url;
url = cfg.url;
callback = callback || cfg.callback;
callerScope = cfg.scope;
if (typeof cfg.timeout != 'undefined') {
this.timeout = cfg.timeout;
}
if (typeof cfg.disableCaching != 'undefined') {
this.disableCaching = cfg.disableCaching;
}
}
if (this.scripts[url]) {
if (typeof callback == 'function') {
callback.call(callerScope || window);
}
return null;
}
Ext.Ajax.request({
url : url,
success : this.processSuccess,
failure : this.processFailure,
scope : this,
timeout : (this.timeout * 1000),
disableCaching : this.disableCaching,
argument : {
'url' : url,
'scope' : callerScope || window,
'callback' : callback,
'options' : cfg
}
});
}
};
ScriptLoaderMgr = function() {
this.loader = new ScriptLoader();
this.load = function(o) {
if (!Ext.isArray(o.scripts)) {
o.scripts = [o.scripts];
}
o.url = o.scripts.shift();
if (o.scripts.length == 0) {
this.loader.load(o);
} else {
o.scope = this;
this.loader.load(o, function() {
this.load(o);
});
}
};
this.loadCss = function(scripts) {
var id = '';
var file;
if (!Ext.isArray(scripts)) {
scripts = [scripts];
}
for (var i = 0; i < scripts.length; i++) {
file = scripts[i];
id = '' + Math.floor(Math.random() * 100);
Ext.util.CSS.createStyleSheet('', id);
Ext.util.CSS.swapStyleSheet(id, file);
}
};
this.addAsScript = function(o) {
var count = 0;
var script;
var files = o.scripts;
if (!Ext.isArray(files)) {
files = [files];
}
var head = document.getElementsByTagName('head')[0];
Ext.each(files, function(file) {
script = document.createElement('script');
script.type = 'text/javascript';
if (Ext.isFunction(o.callback)) {
script.onload = function() {
count++;
if (count == files.length) {
o.callback.call();
}
}
}
script.src = file;
head.appendChild(script);
});
}
};
ScriptMgr = new ScriptLoaderMgr();
Now it can be used this way:
For CSS files loading :
ScriptMgr.loadCss([first.css', 'second.css']);
That is you just need to provide css files path in an array and pass that array to loadCss() function as an argument. No callback is required for CSS files.
For JS file loading :
ScriptMgr.load({
scripts : ['lib/jquery-1.4.2.min.js','lib/jquery.touch-gallery-1.0.0.min.js'],
callback : function() {
//Here you will do those staff needed after the files get loaded
},
scope : this
});
In this case, the same way you entered CSS files, here you just need to put that array of JS files in scripts option. The callback function is called only when all the JS files are loaded successfully. Also, if in any case, the JS files are already loaded in the browser (i.e. already this code is run once), then the control will automatically go to the callback function.

Resources