I need to create a folder structure in FTP similar to that of the tree structure on my view. I want to allow the user to edit the tree structure before creating folders.
I have a TreeView with server binding:
#model IEnumerable<TreeViewItemModel>
#(Html.Kendo().TreeView()
.Name("PipelineStructureMajor")
.BindTo(Model)
.ExpandAll(true)
.DragAndDrop(true)
)
The binding is fine. With some client-side restructuring (appending/dragging/removing some nodes), I want to post the treeview (root node with all its children recursively) to my action.
public ActionResult _CreateFtp(TreeViewItemModel root)
{
//FTPClient in action : Parsing whole tree and converting into the folder structure
return PartialView("_TreeMajor", <refreshed model>);
}
On the Client side, I tried to alert treeview data, it shows the root node text with its Items empty.
$('#createFtpConfirmed').click(function () {
//TreeView data
var treeData = $("#PipelineStructureMajor").data("kendoTreeView").dataSource.data();
alert(JSON.stringify(treeData));
$.ajax({
url:'#Url.Action("_CreateFtp", "Structure")',
data: {root: treeData},
type:"POST",
success: function (result, status, xhr) {
//Doing something useful
}
});
});
Is there a way to accomplish this?
As my question explains, I have 3 steps:
Server-bind the default tree
Edit nodes (delete, add, rename nodes)
Fetch back all treeview data (including added ones)
After going through the kendo docs and this demo, I got the point. I have to make my tree datasource observable so as to reflect the node-changes. For this I had to use kendo-web-scripts (instead of server wrappers). So I changed my step 1 to:
Remote bind the default tree (To make my dataSource observable)
I want my tree view fully loaded at once remotely and seeing this demo, I figured out that treeview only allows one level to be loaded at a time. (UserVoice already queued but Kendo team still ignoring it). So I use a hacky way:
<div id="PipelineStructureMajor"></div>
<button id="createandorinsert" class="k-button hugebtn">Send</button>
<script>
$.get("../Structure/LoadTreeData", function (data) {
var sat = new kendo.data.HierarchicalDataSource({
data: data
});
var pipelinetree = $("#PipelineStructureMajor").kendoTreeView({
dataSource: kendo.observableHierarchy(sat),
dragDrop: true,
select: onNodeSelect
}).data("kendoTreeView");
});
</script>
And I sent my data to the controller action like:
$('#createandorinsert').click(function (e) {
//TreeView's current datasource
var tree = $("#PipelineStructureMajor").data("kendoTreeView").dataSource.data();
$.ajax({
url: '../Structure/FtpCreateAndOrSync',
type: 'POST',
data: {
xmlNodes: JSON.stringify(tree)
},
beforeSend: function (xhr) {
alertSpan.removeClass().addClass("loading");
},
success: function (result, status, xhr) {
alertSpan.removeClass().addClass("success");
},
error: function (jqXhr, textStatus, errorThrown) {
alertSpan.removeClass().addClass("error");
}
});
});
And on the controller side, I Deserialized string json as: Just showing partial code
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult FtpCreateAndOrSync(string xmlNodes)
{
//Deserializing nodes
var xmlNodesModels = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<IEnumerable<XmlNode>>(
xmlNodes).ToArray();
////Alternative
//var data = JsonConvert.DeserializeObject<IEnumerable<XmlNode>>(xmlNodes);
return Json(new { cr = createResult, dr = dbResult });
}
Hope this helps someone.
Related
I'm saving control state and store data to a database (only grids for the moment) and I'm wondering what listener or method I could override to put the data back in place when the controls are being rendered/shown to the user.
Here's how I save the state and store data:
SaveControlState: function (controlItemId, controlType, active, successFunction, scope) {
//Save the control state and its data to server...
var controlState = {};
var control = this.getCmp(controlType + '[itemId=' + controlItemId + ']');
controlState.ControlItemId = control.itemId;
controlState.ControlState = Ext.JSON.encode(control.getState()); //save a JSON string to database...
//if the control has this attribute, it means it wants to update its control state/data...
if (typeof control.controlStateId != 'undefined') controlState.ID = control.controlStateId;
controlState.Active = active;
//if control has a store, let's send data over server...
if (control.getStore() != null) {
controlState.ControlStoreData = [];
Ext.each(control.getStore().data.items, function (record) {
controlState.ControlStoreData.push(record.data);
});
controlState.ControlStoreData = Ext.JSON.encode(controlState.ControlStoreData); //same here...
}
control.setLoading({ msg: Strings.Messages.str_Wait });
//initiate request...
Ext.Ajax.request(
{
url: GlobalVars.Urls.portalControlStateCreateApiUrl,
params: { jsonData: Ext.JSON.encode(controlState), action: 'createcontrolstate' },
method: 'POST',
success: function (result, request) {
//hide spinner...
this.getCmp(controlType + '[itemId=' + controlItemId + ']').setLoading(false);
//if we had a success handler provided, call it...
if (typeof successFunction != 'undefined') successFunction(scope);
},
failure: function (result, request) {
var control = this.getCmp(controlType + '[itemId=' + controlItemId + ']');
control.setLoading(false);
Ext.Msg.show({
title: Strings.UI.str_Error,
msg: Strings.format(Strings.UI.Messages.str_ErrorControlState, control.id, result.responseText),
buttons: Ext.MessageBox.OK,
icon: Ext.MessageBox.ERROR
});
},
scope: this
});
}
And I'm retrieving all the active control state entries when a user logs in:
Ext.create('App.store.ControlState', {
autoLoad: true,
scope: this,
storeId: 'controlStateStore',
proxy: {
type: 'ajax',
extraParams: { activeOnly: true },
url: GlobalVars.Urls.portalControlStateGetAllApiUrl,
headers: { 'Content-type': 'application/json' },
reader: { type: 'json', root: 'ControlStates' }
},
listeners: { load: function (store, records, success, opts) {
//one this is done, I show the rest of the UI....
}
}
});
Now what I need is an override that allows me to peer into the store above and find a record, if I have a match (using the control's itemId attribute) then apply the state and load the data in the store, if the control that's being overriden indeed has a store.
Any ideas what I could use ? Thanks.
Controls implementing Stateful already can use the functions applyState() and getState() exactly for the purpose of saving/restoring state. So these functions are called whenever a control state has to be changed/applied. I think that if you reuse Stateful, ExtJS will handle everything else for you (reloading stores if grid's filters have changed etc.)
But I don't think that you can do a single override to make states work in EVERY component, since each component type would need a special list of what's part of a state. Such a list is not provided by ExtJS; not even for their standard components.
So I fear that you would have to do one override per component type.
If I were you, I would make a store with ID controlStateStore and a model of three and a half fields:
itemId // type string
propertyName // type string
propertyValue // no type(!)
id // auto-created using `convert` to concatenate itemId, a comma and propertyName.
This store would get two added-value functions to load/store a Stateful state object into the model:
getState:function(itemId) {
var records = store.Query("itemId",itemId);
var state = {};
records.each(function rec() {
state[rec.get("propertyName")]=rec.get("propertyValue");
})
return state;
}
setState:function(itemId,state) {
// add one record to the store per property
for(k in state) {
store.add({itemId:itemId,propertyName:k,propertyValue:state[k]});
}
// idProperty id will take care of replacing duplicates
}
These are the functions on the store. Now, on each component type, you would need an override that implements Stateful. So let's have a look at the "recommended" getState/applyState functions, which may look similar to this:
xtype:button,
stateful:true,
stateid:'bla',
getState:function() {
// required if you want to save state
// by default, getState is empty function, so nothing is saved
return {pressed:this.pressed}; // here, a bigger object would be returned
// depending on the component type
},
applyState: function(state) {
// not required, since this function already exists and is the same for alle component types.
if (state) {
Ext.apply(this, state);
}
},
Here, I would replace them both with sth. like:
getState:function() {
if(this.itemId)
Ext.getStore("controlStateStore").setState(this.itemId,{pressed:this.pressed})
},
applyState: function() {
if(this.itemId) {
var state = Ext.getStore("controlStateStore").getState(this.itemId);
Ext.apply(this, state);
}
},
and either add a store.sync() to both, or, the better way, use autoSync on the store.
Although I would opt to keep it compatible by using stateId, not itemId. Since you can reuse ItemIds, but you don't want components take the state of a different component (with same itemId), you should consider to use id or stateId, not itemId.
Disclaimer: Didn't test any of this code, so let's hope it's not too far off the shot...
I have an json response from the server like this for backbone collection
{
"Heading":[
{
"heading1":"heading1",
"heading2":"heading2",
"heading3":"heading3",
"heading4":"heading4",
"heading5":"heading5",
"heading6":"heading6",
"heading7":"heading7"
}
],
"Data":[
{
"column1":98310,
"column2":"spl",
"column3":"sreedhar Laptop",
"column4":"LAPTOP",
"column5":"ACTIVE",
"column6":true,
"column7":"c56e4b92-debe-4c8e-9472-bbb6a322346d"
},
{
"column1":196609,
"column2":"NLP",
"column3":"NLP testing..",
"column4":"LAPTOP",
"column5":"ACTIVE",
"column6":true,
"column7":"7fe2efd4-b93b-4ea8-98a4-7a75d77efb77"
},
{
"column1":262146,
"column2":"venky",
"column3":"venkyLaptop",
"column4":"DESKTOP",
"column5":"INACTIVE",
"column6":false,
"column7":"2e512b95-e2b3-414c-8b40-3bd00b626ae4"
}
]
}
So What I want is to pass only the Data aray for the collection so I have used parse method like this
Customer = Backbone.Collection.extend({
model: customerModel,
url: function () {
return Config.serviceURL("getCusData");
},
parse: function (response) {
return response.Data;
}
});
This is working perfectly but Now I want that Heading array also after I apply fetch So how can I get this Heading array on calling fetch??
There's no rule that says that parse can't stash parts of response in this. For example:
parse: function(response) {
this.headings = response.Heading;
return response.Data;
}
Then later the collection can look at this.headings to see the response.Heading values.
Demo: http://jsfiddle.net/ambiguous/k9aUa/
If you're using other methods (such as reset) to populate your collection then you might need to override those to reset this.headings.
I'm not sure what is the elegant way to pass server variables in to my Model.
For example, i have an id of user that has to be implemented on my Model. But seems like Backbone with require are not able to do that.
My two options are:
Get a json file with Ajax.
Add the variable on my index.php as a global.
Someone know if exists a other way. Native on the clases?
Trying to make work the example of backbonetutorials. I am not able to throw a callback when the method fetch().
$(document).ready(function() {
var Timer = Backbone.Model.extend({
urlRoot : 'timeserver/',
defaults: {
name: '',
email: ''
}
});
var timer = new Timer({id:1});
timer.fetch({
success: function(data) {
alert('success')
},
fail: function(model, response) {
alert('fail');
},
sync: function(data) {
alert('sync')
}
});
});
The ajax request it has been threw. But does not work at all. Because any alert its dispatched.
var UserModel = Backbone.Model.extend({
urlRoot: '/user',
defaults: {
name: '',
email: ''
}
});
// Here we have set the `id` of the model
var user = new Usermodel({id: 1});
// The fetch below will perform GET /user/1
// The server should return the id, name and email from the database
user.fetch({
success: function (user) {
console.log(user);
}
})
The server will reply with a json object then you can leave the rendering part for your backbone. Based on a template for the user.
You may also want to check these out: http://backbonetutorials.com/
I want to map JSON having hierarchical structure onto Model. I can map the data at a top hierarchy onto Model. However, I can't map it onto Model which nested the element which I nested.
JSON
{
"attr1":"data1",
"chi1": {
"attr1":"chi1_data"
},
"list1":[
{"name":"name1"},
{"name":"name2"}
]
}
JavaScript
var Child2 = Backbone.Model.extend({
fun1:function() {
alert("this is Child2");
}
});
var List1 = Backbone.Collection.extend({
url: "list1",
model: Child2,
fun1:function() {
alert("this is List1");
}
});
var Child1 = Backbone.Model.extend({
});
var Root1 = Backbone.Model.extend({
url: "sample.json",
defaults : {
list1 : new List1,
chi1 : new Child1,
}
});
var View1 = Backbone.View.extend({
el: "#friends",
events: {
"click button": "sample"
},
initialize: function() {
this.root1 = new Root1();
},
sample: function() {
this.root1.fetch({
success: function(model) {
// this is success
alert(model.get("attr1"));
// this is error
alert(model.get("list1").fun1());
// this is error too.
model.get("list1").each(function(attr) {
alert(attr.fun1());
});
},
error: function(model, res) {
alert("error: " + res.status);
}
});
},
});
You might want to take a look at this plugin.
http://documentup.com/afeld/backbone-nested/
Might not be exactly what you want, but it could at least point you in the right direction.
The other thing you can do is override the parse method on your model...
parse: function(resp){
// And setup the model using the raw resp
// The resp data is your json from the server and will
// be used to setup the model. So overriding parse, you can
// setup the model exactly they way you want.
return resp;
}
thank you jcreamer.
backbone-nested plugin seems to be different from what I want to do.
I can realize the nest of the model. In using parse function.
// it is able to get "chi1_data"
new Child2(JSON.parse(JSON.stringify(resp["chi1"]))).get("attr1")
// it is able to get "name2"
new Child2(JSON.parse(JSON.stringify(new List1(JSON.parse(JSON.stringify(resp["list1"]))).get(2)))).get("name")
I found Backbone-relational plug in. I will try this
https://github.com/PaulUithol/Backbone-relational
I'm about to make a web app which will have a pretty heavy client end. I'm not sure about the way to organize my javascript code, but here is a basic idea :
// the namespace for the application
var app = {};
// ajax middle layer
app.products = {
add : function(){
// send ajax request
// if response is successful
// do some ui manipulation
app.ui.products.add( json.data );
},
remove : function(){},
...
};
app.categories = {
add : function(){},
....
};
// the ui interface which will be called based on ajax responses
app.ui = {};
app.ui.products = {
add : function( product_obj ){
$('#products').append( "<div id='"+product_obj.id+"'>"+product_obj.title+"</div>" );
}
};
app.ui.categories = {};
Anybody got similar experiences to tell me the pros and cons of this approach? What's your way of designing client side javascript code architecture? Thanks.
[update] : This web app, as you see from the above, deals with products CRUD, categories CRUD only in a ajax fashion. I'm only showing an snippet here, so you guys know what I'm trying to achieve and what my question is. Again, I'm asking for inputs for my approach to organize the code of this app.
That is similar to the way I do my JavaScript projects. Here are some tricks I have used:
Create one file for each singleton object. In your code, store ajax, middle layer and ui interface in separate files
Create a global singleton object for the 3 layers usually in the project; GUI, Backend and App
Never use pure ajax from anywhere outside the Backend object. Store the URL to the serverside page in the Backend object and create one function that uses that URL to contact the server.
Have a JSON class on the server that can report errors and exceptions to the client. In the Backend object, check if the returned JSON object contains an error, and call the serverError function in the GUI class to present the error to the user (or developer).
Here is an example of a Backend object:
var Backend = {};
Backend.url = "/ajax/myApp.php";
Backend.postJSON = function(data, callback){
var json = JSON.stringify(data);
$.ajax({
type: "POST",
url: Backend.url,
data: "json="+json,
dataType: "json",
success: function(response){
if(response){
if(response.task){
return callback(response);
}else if(response.error){
return Backend.error(response);
}
}
return Backend.error(response);
},
error: function(response){
Backend.error({error:"network error", message:response.responseText});
},
});
};
Backend.error = function(error){
if(error.message){
Client.showError(error.message, error.file, error.line, error.trace);
}
};
This can be improved by storing the ajax object somewher in the Backend object, but it's not necessary.
When you build something non trivial, encapsulation is important to make things maintainable in long run. For example, JS UI is not just simple JS methods. A UI components consists of css, template, logic, localization, assets(images, etc).
It is same for a product module, it may need its own settings, event bus, routing. It is important to do some basic architectural code in integrating your chosen set of libraries. This had been a challenge for me when I started large scale JS development. I compiled some best practices in to a reference architecture at http://boilerplatejs.org for someone to use the experience I gained.
For client-side ajax handling I have a URL object that contains all my urls and than I have an ajax object that handles the ajax. This is not a centric approach.In my case I have I have different urls handling different tasks. I also pass a callback function to be executed into the ajax object as well.
var controller_wrapper = {
controller: {
domain: "MYDOMAIN.com",
assets: "/assets",
prefix: "",
api: {
domainer: "http://domai.nr/api/json/info",
tk_check: "https://api.domainshare.tk/availability_check"
},
"perpage": "/listings/ajax",
"save_image": "/members/saveImage",
"update": "/members/update",
"check_domain": "/registrar/domaincheck",
"add_domain": "/registrar/domainadd",
"delete_listing": "/members/deactivateProfile",
"save_listing": "/members/saveProfile",
"get_images": "/images/get",
"delete_image": "/images/delete",
"load_listing": "/members/getProfile",
"load_listings": "/members/getListings",
"loggedin": "/members/loggedin",
"login": "/members/login",
"add_listing": "/members/add",
"remove": "/members/remove",
"get": "/members/get",
"add_comment": "/members/addComment",
"load_status": "/api/loadStatus"
}
}
var common = {
pager: 1,
page: 0,
data: {
saved: {},
save: function (k, v) {
this.saved[k] = v;
}
},
ajax: {
callback: '',
type: 'POST',
url: '',
dataType: '',
data: {},
add: function (k, val) {
this.data[k] = val;
},
clear: function () {
this.data = {};
},
send: function () {
var ret;
$.ajax({
type: this.type,
url: this.url,
data: this.data,
dataType: this.dataType !== '' ? this.dataType : "json",
success: function (msg) {
if (common.ajax.callback !== '') {
ret = msg;
common.ajax.callback(ret);
} else {
ret = msg;
return ret;
}
return;
},
error: function (response) {
console.log(response);
alert("Error");
}
})
}
}
var callback = function (results) {
console.log(results
}
common.ajax.callback = callback;
common.ajax.type = "jsonp";
common.ajax.type = "POST";
common.ajax.url = controller_wrapper.controller.perpage;
common.ajax.add("offset", common.page);
common.ajax.add("type", $("select[name='query[type]']").val());
common.ajax.add("max", $("select[name='query[max]']").val());
common.ajax.add("min", $("select[name='query[min]']").val());
common.ajax.add("bedrooms", $("select[name='query[bedrooms]']").val());
common.ajax.add("sort", $("select[name='query[sort]']").val());
common.ajax.send();