Rearrange items of DataView with Drag and Drop - javascript

I have an ExtJS DataView with following template:
<ul>
<tpl for=".">
<li class="report-field" id="{listId}">
{[this.getReorderLinks(values, xindex, xcount)]}
<span class="field-title small" data-qtip="{displayName}">{displayName}</span>
<i class="field-remove" title="' + deleteLabel + '"></i>
</li>
</tpl>
</ul>
Which makes each list of items look like this:
Where user can click on different icons and perform related action, moving up/down in order and remove.
Note that these items are added to dataview using Drag and Drop, where there's another source dataview container from which I drag the items and add here. While these up/down arrows are working fine with reordering them, I want to reorder these items using drag-n-drop internally.
So, to make each individual item draggable and droppable in the same region, I used refresh event of dataview and registered DNDs there as follows:
listeners: {
'refresh': function(dataview, eOpts) {
var fieldsList = Ext.query('.added-field');
// Iterate over the list and make each item draggable/droppable.
Ext.each(fieldsList,function(field){
var dragSource,
fieldId;
fieldId = field.id;
dragSource = new Ext.dd.DragSource(field, {
isTarget : false
});
dropZone = new Ext.dd.DropTarget(field);
dragSource.dragData = {
record: me.viewStore.findRecord('listId', fieldId),
fieldId: fieldId
};
dropZone.notifyDrop = function(source, event, params) {
var targetRecord = me.viewStore.findRecord('listId', fieldId),
targetRecordIdx = me.viewStore.indexOf(targetRecord),
sourceRecordIdx = me.viewStore.indexOf(params.record);
//Perform rearrangement in the store.
me.viewStore.removeAt(sourceRecordIdx);
me.viewStore.insert(targetRecordIdx, params.record);
return true;
};
});
}
But it is giving me weird behaviours; when I try to drag "Person Email" on top of "Person City", DataView gets broken to look like following:
Also, I get Uncaught TypeError: Cannot read property 'internalId' of undefined when drop operation completes. I even tried to defer calls to removeAt() and insert() by certain ms, but still no luck, moreover, ExtJS has no documentation or working example available for Drag n Drop to Reorder DataView items.
Any help would be appreciated, thanks.

This reminds me a bad experience I had with drag drop in ExtJS 4. Anyway, you may try this plugin. Otherwise, here is something I have tried (scroll is not handled yet) (jsFiddle) :
new Ext.view.DragZone({
view: view,
ddGroup: 'test',
dragText: 'test'
});
new Ext.view.DropZone({
view: view,
ddGroup: 'test',
handleNodeDrop : function(data, record, position) {
var view = this.view,
store = view.getStore(),
index, records, i, len;
if (data.copy) {
records = data.records;
data.records = [];
for (i = 0, len = records.length; i < len; i++) {
data.records.push(records[i].copy(records[i].getId()));
}
} else {
data.view.store.remove(data.records, data.view === view);
}
index = store.indexOf(record);
if (position !== 'before') {
index++;
}
store.insert(index, data.records);
view.getSelectionModel().select(data.records);
}
});

Related

How to display the role name in the dialog box when Delete button is pressed

I've a table specifying the roles and actions, if I check in the role and press delete button, then I should get a dialog box indicating that specific role
If I click on add button, I should get a dialog box or message box with the list of few other roles and on clicking on that role, a rolename should be displayed that has to be added to the respective table
I've created the sap.m.Table and I'm binding the JSON data
Enclosed the Image of the UI:
I've tried with various methods and I'm enclosing my code
Here is the code..
I can delete the item from the table, but I should get a dialog/message box indicating that, the checkmarked role in the table has been deleted
<script>
function delete1()
{
var v = false;
$('input[type="checkbox"]:checked').each(function() {
v = true;
alert("Checked item in the table will be deleted from the table");
});
if (v == false)
{
alert("Please check the item to be deleted");
}
$('input[type="checkbox"]:checked').closest("tr").remove();
}
var oModel = new sap.ui.model.json.JSONModel("JSon/etc5.json");
// Load JSON in model
sap.ui.getCore().setModel(oModel,"model1");
//create table
//"cells"
var oRoles = new sap.m.Text({text: "{model1>Role}"});
var oAction = new sap.m.Button({text: "DETAILS",
type : sap.m.ButtonType.Emphasized,
});
// corresponding columns
var oColAbbr = new sap.m.Column({header: new sap.m.Text({text:"ROLES"}) });
var oColAct = new sap.m.Column({header: new sap.m.Text({text:"ACTION"}) });
// row template
var oRow = new sap.m.ColumnListItem();
oRow.addCell(oRoles).addCell(oAction);
// instantiating the table
var oTab = new sap.m.Table("app",{
inset : true,
headerText : "SOME DATA",
headerDesign : sap.m.ListHeaderDesign.Standard,
includeItemInSelection : false,
});
oTab.addColumn(oColAbbr).addColumn(oColAct);
oTab.bindItems("model1>/emp", oRow); //binding data to the tables
oTab.setMode(sap.m.ListMode.MultiSelect);
var oButton = new sap.m.Toolbar({
content: [
new sap.m.ToolbarSpacer(),
new sap.m.Button({
text : "ADD",
textAlign : "Center",
width : "10%",
type: sap.m.ButtonType.Emphasized,
press: function() {
// oCDialog2.open();
},
}),
new sap.m.Label({text:""}),
new sap.m.Button({
text : "DELETE",
textAlign : "Center",
width : "10%",
type: sap.m.ButtonType.Reject,
press: function() {
// oCDialog1.open();
delete1();
}
}),
]
});
//creating the icons
var iTab = new sap.m.IconTabBar({
items:[
new sap.m.IconTabFilter({
text: "HR",
icon: "sap-icon://group",
content:[oTab]
}),
]
});
var page = sap.m.Page({
content: [iTab,oButton],
showHeader : false,
enableScrolling : true,
});
var app = sap.m.App();
app.addPage(page);
app.placeAt("content");
</script>
It is probably easiest to use the Checkbox control and bind it's value to the same model as where the item lines come from (model1>/emp). In your delete method, you could then easily iterate through the emp array and test on the value representing the checkbox.
Whenever you delete an entry from the array, either UI5's MessageToast or MessageBox controls to show the message. Alerts may be blocked by "Check here to disable alerts from this website" functionality in some browsers.
You may also want to change the $.each to a $.grep instead. This loops through an array in pretty much the same way as $.each with one exception. If you return true from the callback, the element is retained. Otherwise, it is removed from the array.
Your code should look something like this:
items = this.getView().getModel("model1").getProperty("/emp");
items = $.grep(items, function (el, i) {
if (el.propertyBoundToCheckbox) {
MessageToast.show("Deleting entry: " + el.getName())
return false;
}
return true; // keep the element in the array
});
Note: The code above pulls the model from the view, as that's a best practice. Try not to tie anything to the core, as the core is shared between all applications running in the browser window (e.g. in Fiori Launch Pad scenario).

What's the Best Way to Apply Numbering to a Jquery Sortable list?

After trying out a number of methods, I have searched high and low for a good solution to a seemingly easy problem.
I have a sortable list of chapters and I'm using averaging to make jquery sortable work for a meteor application. The dragging and dropping sortable part is easy (using averaging), but applying the correct (whole number) order of the chapters has been tricky.
A common application would be chapter numbers applied to a table of contents.
What I've tried:
Rewriting the stop: function using a for loop that accounts for the different scenarios. Drag to front, drag to rear
This can get complex as the numbers of chapters increase. I felt it was too complex a solution.
Using jQuery to identify the order of the items and apply the numbers according to their sorted order in the browser. I think this will work, but I haven't been able to figure out what jquery functions to use and where after trying several out. Disclaimer: I'm new to Spacebars and haven't used much jquery.
chaptersList html:
<template name="chaptersList">
<div id="items">
{{#each publishedChapters}}
{{> chapterItem}}
{{/each}}
</div><!--items-->
</template>
chaptersList js:
Template.chaptersList.rendered = function() {
this.$('#items').sortable({
stop: function(e, ui) {
// get the dragged html element and the one before
// and after it
el = ui.item.get(0)
before = ui.item.prev().get(0)
after = ui.item.next().get(0)
if(!before) {
//if it was dragged into the first position grab the
// next element's data context and subtract one from the order
newOrder = 1;
} else if(!after) {
//if it was dragged into the last position grab the
// previous element's data context and add one to the order
newOrder = Blaze.getData(before).order + 1
}
else
//else take the average of the two orders of the previous
// and next elements
newOrder = (Blaze.getData(after).order +
Blaze.getData(before).order)/2
newOrder = Math.round(newOrder)
//update the Items' orders
Chapters.update({_id: Blaze.getData(el)._id}, {$set: {order: newOrder}})
}
})
}
Template.chaptersList.helpers({
publishedChapters: function() {
return Chapters.find({ published: true },
{ sort: {order: 1}
});
},
items: function() {
return Chapters.find({}, {
sort: {order: 1 }
})
}
});
chapterItem html:
<template name="chapterItem">
<div class="item">
<h3 class="headroom-10 chapter-title-small">
{{title}}
</h3>
<p class="chapter-text">{{#markdown}}{{chapterTease}}{{/markdown}}</p>
{{#if ownChapter}}
Edit
<span class="delete">
Delete
</span>
{{/if}}
</div>
</template>
Thank you for your valuable insight.
I went through this same issue and the trick to get it working cleanly in my case was to cancel the sortable action from inside the sort and let Blaze take over once the item was dropped. Otherwise the sortable ordering and Blaze ordering end up wrestling one another.
I save my new set of ordered items as a single batch, but that's not necessarily required.
Template.builder.rendered = function(){
var self = this;
this.$("ol.sortable").sortable({
axis: "y",
containment: "ol.sortable",
tolerance: "pointer",
update: function(event, ui){
var
items = $(this).find("li"),
picks = _.map(items, function(item){
var obj = Blaze.getData(item);
obj.position = $(item).index() + 1;
return obj;
});
self.$('ol.sortable').sortable('cancel');
Meteor.call(
"orderPicks",
team,
picks,
function(error, result){}
);
}
});
}
And on the server:
Meteor.methods({
orderPicks: function(team, picks){
Teams.update(team,
{ $set: {picks: picks} }
);
return true;
}
});
I believe I picked this up from another SO thread; I can't remember. In any case it works seamlessly for a modestly sized set of items.
HTML:
<template name="builder">
<ol class="builder sortable">
{{#each team.picks}}
<li>{{position}}. {{name}}</li>
{{/each}}
</ol>
</template>
This answer is a follow up to the conversation with #jeremy above, adapting a nested array approach to individual documents.
It is not currently working, but by posting it here, I hope to be able to gain insight, get it working and offer another solution.
Right now, the sorting order is not changing when an item is dragged and dropped. If you can see why, let me know or you can also edit the answer:
Add id to li:
chapterId: function() {
return Chapters.findOne(this._id)._id
}
HTML:
<li class="item headroom-20" id="{{chapterId}}">
Sortable JS:
Template.chaptersList.rendered = function(){
var self = this;
this.$("ol.items").sortable({
axis: "y",
containment: "ol.items",
tolerance: "pointer",
update: function(event, ui){
var items = $(".items").find("li");
var publishedChaptersCount = items.length;
var sortedIds = [];
for (var i = 0; i < publishedChaptersCount; i++) {
var obj = $(items[i]).attr('id');
sortedIds.push(obj);
}
self.$('ol.items').sortable('cancel');
Meteor.call(
"chapterOrderUpdate",
sortedIds,
function(error, result){}
);
}
});
}
Meteor Method:
Meteor.methods({
chapterOrderUpdate: function(sortedIds){
var publishedChaptersCount = sortedIds.length;
for (var i = 0; i < publishedChaptersCount; i++) {
var chapterId = sortedIds[i];
Chapters.update({_id: chapterId}, { $set: { order: i+1 } })
}
}
});

Ext js grid remove selection model

I have a situation where I need to dynamically add or remove grids selection model.
Searching the documentation I see that the selection model doesn't have a destroy() method or anything similar. How can I remove or destroy a selection model from a grid in ext js 4.x.?
If this is not possible I still have an option to revert some functionallity and dynamically add the selection model to an already created grid. But I'm also not sure if this is possible.
I'd suggest to disable the selection model instead of destroying it.
You can clear the current selection (deselectAll) and lock the selection model to prevent further selection (setLocked):
selModel.deselectAll();
selModel.setLocked(true);
As you're using a checkbox selection model, you'll also need to hide the corresponding column which is added to the grid:
grid.headerCt.child('gridcolumn[isCheckerHd]').hide();
Selection models are not designed to be replaced, so... it's gonna be complicated!
You'd have to reproduce the initialization of the sel model, unwire the previous one, and rewire the new one...
Here's an example that works in substituting a row selection model for a checkbox model. It may still contains memory leaks from listeners registered by the first selection model that I would have forgot. The creation of the new selection model relies on the getSelectionModel method of the grid, which implements the disableSelection, simpleSelect, and multiSelect options of the grid (see the code).
Ext.widget('grid', {
renderTo: Ext.getBody()
,store: ['Foo', 'Bar', 'Baz']
,selType: 'checkboxmodel'
,columns: [{
dataIndex: 'field1'
,text: "Name"
}]
,listeners: {
selectionchange: function(sm, records) {
var grid = sm.view.up(),
item = grid.down('tbtext');
if (records.length) {
item.setText(
'Selection: ' + Ext.pluck(Ext.pluck(records, 'data'), 'field1').join(', ')
);
} else {
item.setText('No selection');
}
}
}
,tbar: [{
text: "Replace selection model"
,handler: function(button) {
var grid = button.up('grid'),
headerCt = grid.headerCt,
checkColumn = headerCt.down('[isCheckerHd]'),
view = grid.view,
previous = grid.selModel,
sm;
// try to clean up
previous.deselectAll();
previous.destroy();
// sel model doesn't clear the listeners it has installed in its
// destroy method... you'll have to chase the listeners that are
// installed by the specific type of sel model you're using
if (previous.onRowMouseDown) {
view.un('itemmousedown', previous.onRowMouseDown, previous);
}
if (previous.onRowClick) {
view.un('itemclick', previous.onRowClick, previous);
}
// clear references
delete grid.selModel;
delete view.selModel;
// create another selModel
grid.selType = 'rowmodel';
//grid.disableSelection = true;
sm = grid.getSelectionModel();
// assign new sel model
view.selModel = sm;
sm.view = view;
// remove checkbox model column
if (checkColumn) {
headerCt.remove(checkColumn);
}
// init sel model is trigerred in view render events, so we must do it
// now if the view is already rendered
if (view.rendered) {
sm.beforeViewRender(view);
sm.bindComponent(view);
}
// finally, refresh the view
view.refresh();
}
}]
// a place to display selection
,bbar: [{
xtype: 'tbtext'
,text: 'No selection'
}]
});

Nested Templates in JavaScript/jQuery/AJAX

I am trying to accomplish a module where i need to use nested templates and I am stuck at HOW can i do that.
basically there are 3 levels in my UI, for example say Level 1, Level 2, Level 3.
So when the page is displayed i need to render level 1 only.
But when user clicks on "expand" button of any element of level1 i need to render corresponding elements of Level 2 (not all) below the selected element of level 1.
Now when user clicks on "expand" of any element of Level 2, corresponding Level 3 should be rendered..
To summarize it should be just like Windows Explorer's navigation bar on left.!
Generally, you should define separate components for each level, assign template to each of your components and implement something like expand()/collapse() methods. If a component is initially collapsed (your case) then it shouldn't need to render child items on initialization, it would render them only when you expand them (the appropriate templates of child components would be used).
Please provide a basic code that you are trying to make work, it would be easier to help you that way.
Here is a quick prototype of Widget system with a simple rendering flow that uses templates. I guess you want something like that in your application. It is unoptimized, it's just an idea of how your framework might look.
/**
* Widget constructor
*/
var Widget = function(config) {
// apply config
$.extend(this, config);
// attempt to render
this.render();
};
/**
* Widget prototype
*/
$.extend(Widget.prototype, {
// render target
renderTo: null,
// template
tpl: '<div class="container-panel">' +
'<p>${txt}</p>' +
'<div class="items-container"></div>' +
'</div>',
// template data
tplData: null,
// child items array
children: null,
// initial collapsed state
collapsed: false,
// widget's root element
el: null,
// default render target selector for child items
renderTarget: '.items-container',
render: function() {
var me = this,
renderDom
// render the widget
if(!this.rendered && this.renderTo && this.tpl) {
renderDom = $.tmpl(this.tpl, this.tplData);
// assume that first element is widget's root element
this.el = renderDom[0];
$(this.renderTo).append(renderDom);
// clear the reference
renderDom = undefined;
// THIS IS JUST EXAMPLE CODE! Bind click handler...
$(this.el).find('p').first().click(function() {
me.collapsed ? me.expand() : me.collapse();
});
// find render target for children
this.renderTarget = $(this.el).find(this.renderTarget).first();
// render children if not collapsed
this.renderChildren();
// set rendered flag
this.rendered = true;
}
},
renderChildren: function() {
var children = this.children;
if(!this.collapsed && children && children.length) {
for(var i = 0, len = children.length; i < len; i++) {
// render children inside
children[i].renderTo = this.renderTarget;
children[i].render();
}
}
},
/**
* Expand template method. Override it.
*/
expand: function() {
this.collapsed = false;
this.renderChildren();
this.renderTarget.show();
},
/**
* Collapse template method. Override it.
*/
collapse: function() {
this.collapsed = true;
this.renderTarget.hide();
}
});
​Here I pre-defined the templates and hardcoded the expanding/collapsing logic that happens on click inside widget's first paragraph element.
This is how you would use the widgets:
// Using our widgets
var containerPanel = new Widget({
tplData: {txt: 'Hello world!'},
renderTo: $('body'),
collapsed: true,
children: [
new Widget({
tplData: {txt: ' Child 1'},
collapsed: true,
children: [
new Widget({
tplData: {txt: ' Child 1.1'}
}),
new Widget({
tplData: {txt: ' Child 1.2'}
}),
new Widget({
tplData: {txt: ' Child 1.3'}
})
]
}),
new Widget({
tplData: {txt: ' Child 2'}
})
]
});
You can see a live example on jsFiddle: http://jsfiddle.net/dipish/XDmWq/
Just click on items and look at the dynamically generated markup.
I think the code is self-explanatory but feel free to ask any questions. Note that the code uses jQuery Templates Plugin but it is just for convenience.
If you have many complex components in your web app you may want to use something more serious than bare jQuery, like ExtJS or Dojo Toolkit. Such frameworks typically provide you a convenient class system and base widget/component logic to build on, besides lots of other things.
Good luck!
You'll need to be a bit more specific in what the markup will look like. However, here's a rough example:
//run this puppy when we need to append stuff
var dynamicAppend = function( data, container )
{
var ul = container.children().slice(1,2);
var len = data.length;
for( var i = 0; i < len; i++ )
{
var markup = [
"<li class='" + data[i].thing + "'>",
"<span class='second_toggle' data-stuff='" + data[i].other_thing + "'></span>",
"<ul>",
"</ul>",
"</li>"
];
ul.append( markup.join() );
}
}
//do ajax stuff
var handleAjax = function( data, container )
{
var json = { unique: data }
$.ajax({
url: '',
data: json,
success: function( data )
{
if( data.success === 'your_flag' && data.newStuff )
{
dynamicAppend( data.newStuff, container );
}
}
});
}
//first, you'll click the toggle
var expand_toggle = $( '.toggle' );
expand_toggle.click(function(e){
var that = $(this);
//grab some data that identifies the unique container you want to append to
var unique_id = that.data( 'some_identifier' );
var container = that.parents('.parent_container_class:first');
handleAjax( unique_id, container );
});
I would personally put this into a constructor and do it OOP style, but you can get the idea.
Here's some markup:
<div class='parent_container_class'>
<span class='toggle' data-some_identifier='special_identifier_here'></span>
<ul></ul>
</div>

Drag and drop sortable ng:repeats in AngularJS?

Is it at all easy to use jQuery.sortable on ng-repeat elements in AngularJS?
It would be awesome if re-ordering the items automatically propagated that ordering back into the source array. I'm afraid the two systems would fight though. Is there a better way to do this?
Angular UI has a sortable directive,Click Here for Demo
Code located at ui-sortable, usage:
<ul ui-sortable ng-model="items" ui-sortable-update="sorted">
<li ng-repeat="item in items track by $index" id="{{$index}}">{{ item }}</li>
</ul>
$scope.sorted = (event, ui) => { console.log(ui.item[0].getAttribute('id')) }
I tried to do the same and came up with the following solution:
angular.directive("my:sortable", function(expression, compiledElement){
return function(linkElement){
var scope = this;
linkElement.sortable(
{
placeholder: "ui-state-highlight",
opacity: 0.8,
update: function(event, ui) {
var model = scope.$tryEval(expression);
var newModel = [];
var items = [];
linkElement.children().each(function() {
var item = $(this);
// get old item index
var oldIndex = item.attr("ng:repeat-index");
if(oldIndex) {
// new model in new order
newModel.push(model[oldIndex]);
// items in original order
items[oldIndex] = item;
// and remove
item.detach();
}
});
// restore original dom order, so angular does not get confused
linkElement.append.apply(linkElement,items);
// clear old list
model.length = 0;
// add elements in new order
model.push.apply(model, newModel);
// presto
scope.$eval();
// Notify event handler
var onSortExpression = linkElement.attr("my:onsort");
if(onSortExpression) {
scope.$tryEval(onSortExpression, linkElement);
}
}
});
};
});
Used like this:
<ol id="todoList" my:sortable="todos" my:onsort="onSort()">
It seems to work fairly well. The trick is to undo the DOM manipulation made by sortable before updating the model, otherwise angular gets desynchronized from the DOM.
Notification of the changes works via the my:onsort expression which can call the controller methods.
I created a JsFiddle based on the angular todo tutorial to shows how it works: http://jsfiddle.net/M8YnR/180/
This is how I am doing it with angular v0.10.6. Here is the jsfiddle
angular.directive("my:sortable", function(expression, compiledElement){
// add my:sortable-index to children so we know the index in the model
compiledElement.children().attr("my:sortable-index","{{$index}}");
return function(linkElement){
var scope = this;
linkElement.sortable({
placeholder: "placeholder",
opacity: 0.8,
axis: "y",
update: function(event, ui) {
// get model
var model = scope.$apply(expression);
// remember its length
var modelLength = model.length;
// rember html nodes
var items = [];
// loop through items in new order
linkElement.children().each(function(index) {
var item = $(this);
// get old item index
var oldIndex = parseInt(item.attr("my:sortable-index"), 10);
// add item to the end of model
model.push(model[oldIndex]);
if(item.attr("my:sortable-index")) {
// items in original order to restore dom
items[oldIndex] = item;
// and remove item from dom
item.detach();
}
});
model.splice(0, modelLength);
// restore original dom order, so angular does not get confused
linkElement.append.apply(linkElement,items);
// notify angular of the change
scope.$digest();
}
});
};
});
Here's my implementation of sortable Angular.js directive without jquery.ui :
https://github.com/schartier/angular-sortable
you can go for ng-sortable directive which is lightweight and it does not uses jquery. here is link ng-sortable drag and drop elements
Demo for ng-sortable

Categories