I'm thinking of implementing an "Amazon Wishlist-ish" undo functionality to my app.
I mean...
then click 'Delete Item'
by clicking 'Undo', the deletion appears to be canceled
My list controller is currently looking like this,
function ListsController($scope, List) {↲
List.get({}, function(lists) {
$scope.lists = lists.objects;
$scope.delete_list = function(index) {
var isConfirmed = confirm('Are you sure you want to delete it?');
if (isConfirmed) {
var targetlist = $scope.lists[index];
List.delete({ listId: targetlist.id },
function(list) {
$scope.lists.splice(index, 1);
}
)
}
}
});
};
But I wanna enable undo feature as I said.
What is the best way to do that in angular js?
I can't give an exact example without seeing what the List service does, but I think your best bet is to keep track of removed items in another scope variable and if the undo is clicked you just add it back to lists.
Maybe something like:
$scope.deleted_lists = [];
$scope.delete_list = function(index) {
var isConfirmed = confirm('Are you sure you want to delete it?');
if (isConfirmed) {
var targetlist = $scope.lists[index];
List.delete({ listId: targetlist.id }, function(list) {
$scope.deleted_lists.concat($scope.lists.splice(index, 1));
});
}
};
Then you can use an ng-repeat (if you want multiple undos) to display the deleted items and a click on the undo button could simply add the item back to the list via .push().
Related
In my web app, I have a page where users can copy books from their library.
On the website, when a user clicks the copy button, the app executes this bit of Backbone.js code:
clonebook: function () {
var self = this;
this.book.clone().then((r) => {
self.model.collection.add(r);
});
},
In My SQL Server database, book looks like this:
bookId, bookTitle, authorId, libraryId, rowOrderNumber
The problem is, if the user tries to clone multiple books really fast by hitting the copy button, the rowOrderNumber can get out of whack.
Is there a way to tell the Backbone clone method to wait until the database or server has completed a clone process before going on to the next one?
Thanks!
I didn't use backbone, but
clonebook: function () {
var self = this;
self.loading = true
this.book.clone().then((r) => {
self.model.collection.add(r).then(() => {
self.loading = false
});
});
},
Not you have to use this loading somehow to disable the button
The most common UX pattern for this is to disable the button when the process starts, and enable when finished.
clonebook: function () {
var self = this;
// disable clone button
this.book.clone().then((r) => {
self.model.collection.add(r);
// enable clone button
});
},
I have a kendo grid, and when an item is selected I want to modify the underlying dataitem so i'm doing this ...
selectionChange: function(e)
{
var component = $(this.table).closest('.component');
var grid = this;
var val = !component.hasClass("secondary");
var selection = grid.dataItems(grid.select());
selection.forEach(function () {
this.set("SaleSelected", val);
});
}
I also have 2 buttons that allow me to push items between the 2 grids which do this ...
select: function (e) {
e.preventDefault();
var sender = this;
// get kendo data source for the primary grid
var source = $(sender).closest(".component")
.find(".component.primary")
.find(".details > [data-role=grid]")
.data("kendoGrid")
.dataSource;
// sync and reload the primary grid
source.sync()
.done(function () {
source.read();
my.Invoice.reloadGridData($(sender).closest(".component").find(".component.secondary").find(".details > [data-role=grid]"));
});
return false;
},
deselect: function (e) {
e.preventDefault();
var sender = this;
debugger;
// get kendo data source for the secondary grid
var source = $(sender).closest(".component")
.find(".component.secondary")
.find(".details > [data-role=grid]")
.data("kendoGrid")
.dataSource;
// sync and reload the primary grid
source.sync()
.done(function () {
source.read();
my.Invoice.reloadGridData($(sender).closest(".component").find(".component.primary").find(".details > [data-role=grid]"));
});
return false;
}
Essentially the "selected items" from grid1 can be marked as such on the server then the grids get reloaded to move the items over.
All good I thought, but apparently Kendo has other ideas.
Editing a data item causes its owning grid to rebind losing the selection state resulting in some confusing behaviour for the user.
Is there a way to tell kendo "i'm going to edit this unbound property right now, don't go messing with binding"?
Ok it turns out kendo is a bit of a wierdo and I still have no idea why they insist you call all their "api stuff" to do simple tasks when doing things more directly actually works better.
In my case I removed the selection change call altogether and let kendo handle that, then in my selection button handlers to move the data between grids I updated the properties directly on the data items instead of calling
"item.set("prop", value)" i now have to do "item.prop = value".
The net result is this ...
select: function (e) {
e.preventDefault();
var sender = this;
// get some useful bits
var component = $(sender).closest(".component");
var primaryGrid = component.find(".component.primary").find(".details > [data-role=grid]").data("kendoGrid");
// get the new selection, and mark the items with val
var selection = $(primaryGrid.tbody).find('tr.k-state-selected');
selection.each(function (i, row) {
primaryGrid.dataItem(row).SaleSelected = true;
primaryGrid.dataItem(row).dirty = true;
});
// sync and reload the primary grid
primaryGrid.dataSource.sync()
.done(function () {
primaryGrid.dataSource.read();
component.find(".component.secondary")
.find(".details > [data-role=grid]")
.data("kendoGrid")
.dataSource
.read();
});
return false;
},
deselect: function (e) {
e.preventDefault();
var sender = this;
// get some useful bits
var component = $(sender).closest(".component");
var secondaryGrid = component.find(".component.secondary").find(".details > [data-role=grid]").data("kendoGrid");
// get the new selection, and mark the items with val
var selection = $(secondaryGrid.tbody).find('tr.k-state-selected');
selection.each(function (i, row) {
secondaryGrid.dataItem(row).SaleSelected = false;
secondaryGrid.dataItem(row).dirty = true;
});
// sync and reload the primary grid
secondaryGrid.dataSource.sync()
.done(function () {
secondaryGrid.dataSource.read();
component.find(".component.primary")
.find(".details > [data-role=grid]")
.data("kendoGrid")
.dataSource
.read();
});
return false;
}
kendo appears to be taking any call to item.set(p, v) as a trigger to reload data so avoiding the kendo wrapper and going directly to the item properties allows me direct control of the process.
Moving the code from the selection change event handler to the button click handler also means i only care about that data being right when it actually needs to be sent to the server, something I just need to be aware of.
I don't like this, but it's reasonably clean and the ui shows the right picture even if the underlying data isn't quite right.
My other option would be to create a custom binding but given that the binding would have to result in different results depending on weather it was binding to the primary or the secondary grid I suspect that would be a lot of js code, this feels like the lesser of 2 evils.
I think you can bind the dataBinding event to just a "preventDefault" and then unbind it and refresh at your leisure
var g = $("#myGrid").data("kendoGrid");
g.bind("dataBinding", function(e) { e.preventDefault(); });
then later...
g.unbind("dataBinding");
I have developed the gridview application using the widget (from this code)
https://github.com/pablorr18/TiDynamicGrid
i have modified the code as per my client requirements. Now if i like to click the view cart button on the list item it will alert the message like "Am clicked". But i tried in various ways.but i can't find the solutions. Can anyone please explain me how we are write the code for this.
I have follows below code in my application:
var items = [];
var showGridItemInfo = function(e){
alert("Onclick the row");
};
var delay = (OS_ANDROID) ? 1000:500;
$.tdg.init({
columns:3,
space:5,
delayTime:delay,
gridBackgroundColor:'#e1e1e1',
itemBackgroundColor:'#fff',
itemBorderColor:'transparent',
itemBorderWidth:0,
itemBorderRadius:5,
onItemClick: showGridItemInfo
});
function createSampleData(){
var sendit = Ti.Network.createHTTPClient({
onerror: function(e){
Ti.API.debug(e.error);
alert('There was an error during the connection');
},
timeout:10000,
});
sendit.open('GET', url+'android_livedev/client/test.php?action=listitems&categoryid='+subcategorylist_category_id+'&productmin=0&productmax=50');
sendit.send();
sendit.onload = function(){
var response = JSON.parse(this.responseText);
if(response[0].success == 0){
alert("No Products Found");
}
else {
items = [];
for (var x=0;x<response[0].data.length;x++){
var view = Alloy.createController('item_layout',{
image:imageurl+response[0].data[x].thumb_image,
product:response[0].data[x].product,
productprice:"$"+" "+response[0].data[x].price,
onItemClick: addcart,
}).getView();
var values = {
product: response[0].data[x].product,
image: response[0].data[x].thumb_image,
productid : response[0].data[x].productid,
};
items.push({
view: view,
data: values
});
};
$.tdg.addGridItems(items);
reateSampleData();
$.tdg.clearGrid();
$.tdg.init({
columns:nColumn,
space:nSpace,
delayTime:delay,
gridBackgroundColor:'#e1e1e1',
itemHeightDelta: 0,
itemBackgroundColor:'#fff',
itemBorderColor:'transparent',
itemBorderWidth:0,
itemBorderRadius:5,
onItemClick: showGridItemInfo
});
createSampleData();
});
$.win.open();
The item_layout.xml code is looking like :
<Alloy>
<View id="mainView">
<ImageView id="thumb"/>
<Label id="product"></Label>
<Label id="productprice"></Label>
<Button id="addcart" onClick="additemtocart"/>
</View>
</Alloy>
EDIT:
now am getting the view and the button id from that specified view. But if am trying to click the button means can't able to do. can you check my code and give a solution.
i have added the below code :
var view = Alloy.createController('item_layout',{
image:imageurl+response[0].data[x].thumb_image,
product:response[0].data[x].product,
productprice:"$"+" "+response[0].data[x].price
}).getView();
var controller = Alloy.createController('item_layout');
var button = controller.getView('addcart');
button.addEventListener('click', function (e){
alert("click");
Ti.API.info('click');
});
First switch the ids to classes because they're non-unique. Only use ID's if you must. Second, try something like this:
Try something like this:
$('.addCart').click(function() {
var item = $(this).silblings('.product').text;
addItemToCart(item);
});
Even better, add a data-productId attribute to the shopping cart button and you could do something like this:
$('.addCart').click(function() {
var itemId = $(this).attr('data-productId');
addItemToCart(itemId);
});
This is better since you're only requiring the shopping cart button to be on the screen.
All this said, you also probably need to include a fallback that adds the item to the cart when javascript isn't available on the page.
I have a sortable accordion loaded with a foreach-template loop over a ko.observableArray() named "Tasks".
In the accordion I render the TaskId, the TaskName, and a task Description - all ko.observable().
TaskName and Description is rendered in input/textarea elements.
Whenever TaskName or Description is changed, an item is de-selected, or another item is clicked on, I want to call a function saveEdit(item) to send the updated TaskName and Description to the database via an ajax request.
I need to match the TaskId with the Tasks-array to fetch the actual key/value-pair to send to the saveEdit().
This is the HTML:
<div id="accordion" data-bind="jqAccordion:{},template: {name: 'task-template',foreach: Tasks,afteradd: function(elem){$(elem).trigger('valueChanged');}}"></div>
<script type="text/html" id="task-template">
<div data-bind="attr: {'id': 'Task' + TaskId}" class="group">
<h3><b><span data-bind="text: TaskId"></span>: <input name="TaskName" data-bind="value: TaskName /></b></h3>
<p>
<label for="Description" >Description:</label><textarea name="Description" data-bind="value: Description"></textarea>
</p>
</div>
</script>
This is the binding:
ko.bindingHandlers.jqAccordion = {
init: function(element, valueAccessor) {
var options = valueAccessor();
$(element).accordion(options);
$(element).bind("valueChanged",function(){
ko.bindingHandlers.jqAccordion.update(element,valueAccessor);
});
},
update: function(element,valueAccessor) {
var options = valueAccessor();
$(element).accordion('destroy').accordion(
{
// options put here....
header: "> div > h3"
, collapsible: true
, active: false
, heightStyle: "content"
})
.sortable({
axis: "y",
handle: "h3",
stop: function (event, ui) {
var items = [];
ui.item.siblings().andSelf().each(function () {
//compare data('index') and the real index
if ($(this).data('index') != $(this).index()) {
items.push(this.id);
}
});
// IE doesn't register the blur when sorting
// so trigger focusout handlers to remove .ui-state-focus
ui.item.children("h3").triggerHandler("focusout");
if (items.length) $("#sekvens3").text(items.join(','));
ui.item.parent().trigger('stop');
}
})
.on('stop', function () {
$(this).siblings().andSelf().each(function (i) {
$(this).data('index', i);
});
})
.trigger('stop');
};
};
My first thought was to place the line
$root.SelectedTask( ui.options.active );
in an .on('click') event function where SelectedTask is a ko.observable defined in my viewModel. However, the .on('click') event seems to be called a lot and it's generating a lot of traffic. Also, I can´t quite figure out where to put the save(item) call that sends the selected "item" from Tasks via an ajax-function to the database.
Any help is highly appreciated. Thanks in advance! :)
Whenever TaskName or Description is changed, an item is de-selected, or another item is clicked on, I want to call a function saveEdit(item) to send the updated TaskName and Description to the database via an ajax request.
This sounds like the core of what you want to do. Let's start out with a Task model
function Task (data) {
var self = this;
data = data || {};
self.id = ko.observable(data.id);
self.name = ko.observable(data.name);
self.description = ko.observable(data.description);
}
And then we need our View Model:
function ViewModel () {
var self = this;
self.tasks = ko.observableArray();
self.selectedTask = ko.observable();
self.saveTask = function (task) {
$.ajax({ ... });// ajax call that sends the changed data to the server
};
var taskSubscription = function (newValue) {
self.saveTask(self.selectedTask());
};
var nameSubscription, descriptionSubscription;
self.selectedTask.subscribe(function (newlySelectedTask) {
if (newlySelectedTask instanceof Task) {
nameSubscription =
newlySelectedTask.name.subscribe(taskSubscription);
descriptionSubscription =
newlySelectedTask.description.subscribe(taskSubscription);
self.saveTask(newlySelectedTask);// But why?
}
});
self.selectedTask.subscribe(function (currentlySelectedTask) {
if (currentlySelectedTask instanceof Task) {
nameSubscription.dispose();
descriptionSubscription.dispose();
self.saveTask(currentlySelectedTask);// But why?
}
}, null, 'beforeChange');
}
So what's going on here? Most of this should be pretty self explanatory so I'm just going to focus on the subscriptions. We created a taskSubscription function so we're not constantly having it defined every time the self.selectedTask changes.
We have two subscriber functions. The first fires after the selectedTask's value has changed and the second fires before it changes. In both, we verify that the new value is an instance of a Task object. In the after change subscription, we set up two subscriptions on the name and description properties. Then I capture the return value from the subscription function into two private variables. These are used in the before change function to dispose of those subscriptions so that if those Tasks are ever updated when they're not currently selected, then we don't continue to fire off the saveTask function.
I've also added self.saveTask in each of the subscriptions to the selectedTask observable. I asked why in here because, why save it if we don't know if the value has changed or not? You may be making ajax requests needlessly here.
Also, as demonstrated by this code, you can set up these subscriptions to make ajax requests every time the value changes but that may end up making a LOT of requests. A better option might be to set up functionality in your Task model that can track whether or not it is 'dirty' or not. Meaning one or more of its values have changed that requires updating.
function Task (data) {
var self = this;
// Make a copy of the data object coming in and use this to save previous values
self._data = data = $.extend(true, { id: null, name: null, description: null }, data);
self.id = ko.observable(data.id);
self.name = ko.observable(data.name);
self.description = ko.observable(data.description);
for (var prop in data) {
if (ko.isSubscribable(self[prop])) {
self[prop].subscribe(function (oldValue) {
data[prop] = oldValue;
}, null, 'beforeChange');
}
}
}
Task.prototype.isDirty = function () {
var self = this;
for (var prop in self._data) {
if (ko.isSubscribable(self[prop])) {
if (self._data[prop] !== self[prop]())
return true;
}
}
return false;
};
And of course you need a way to save it, or make it not dirty
Task.prototype.save = function () {
var self = this;
for (var prop in self._data) {
if (ko.isSubscribable(self[prop])) {
self._data[prop] = self[prop]();
}
}
};
Using the same concept you can also create Task.prototype.revert that does the opposite of what .save does. With all this in place, you could forego setting up the subscriptions on the individual name and description properties. I wanted to show that option to just demonstrate how one might want to use the .dispose method on a subscription. But now you can just subscribe to the selectedTask observable ('beforeChange') and see if the currently selected task that you're about to swap out isDirty. If it is, call the saveTask function, and when that completes, call the .save function on the Task so that it is no longer dirty.
This is probably the route I would go in implementing something like this. The beauty of it is, I haven't written a single line of code that has anything to do with the manipulating the View. You can set the selectedTask any way you see fit. What I would do is, bind the selectedTask observable to a click binding on the <h3> element inside of the accordion. That way, every time a user clicks on any of the accordions, it will potentially save the previously selected task (if any of the property values had changed).
Hopefully that addresses your scenario here of trying to save a Task when certain events are triggered.
I have list of item IDs on page load:
var itemIds = [1, 5, 10, 11];
IDs are rendered into list and shown to user. I have function which loads detailed information about item, it returns Promise/A.
function loadInfo(id) { ... }
Loading of info for all items is initiated on page load:
val infos = {};
$.each(itemIds, function(i, id) { infos[id] = loadInfo(id); }
Now the problem itself.
When user clicks on item ID:
if item info is loaded, info must be shown
if item info is not yet loaded, it must be shown when it is loaded
Looks easy:
$('li').click(function() {
var id = $(this).data('item-id');
infos[id].then(function(info) { $('#info').text(info); });
});
But if user cliked on another item before current one is loaded, then I have to cancel handler on current item promise and schedule on new one.
How to properly do it?
I have several working solutions (like maintaining currentItemId variable and checking it in promise handler), but they are ugly. Should I look to reactive programming libraries instead?
Kriomant,
I guess there's a number of ways you could code this. Here's one :
var INFOS = (function(){
var infoCache = {},
promises = {},
fetching = null;
var load = function(id) {
if(!promises[id]) {
promises[id] = $ajax({
//ajax options here
}).done(function(info){
infoCache[id] = info;
delete promises[id];
});
}
return promises[id];
}
var display = function(id, $container) {
if(fetching) {
//cancel display (but not loading) of anything latent.
fetching.reject();
}
if(infoCache[id]) {
//info is already cached, so simply display it.
$container.text(infoCache[id]);
}
else {
fetching = $.Deferred().done(function() {
$container.text(infoCache[id]);
});
load(id).done(fetching.resolve);
}
}
return {
load: load,
display: display
};
})();
As you will see, all the complexity is bundled in the namespace INFOS, which exposes two methods; load and display.
Here's how to call the methods :
$(function() {
itemIds = ["p7", "p8", "p9"];
//preload
$.each(itemIds, function(i, id) { INFOS.load(id); });
//load into cache on first click, then display from cache.
$('li').on('click', function() {
var id = $(this).data('item-id');
INFOS.display(id, $('#info'));
});
});
DEMO (with simulated ajax)
The tricks here are :
to cache the infos, indexed by id
to cache active jqXHR promises, indexed by id
to display info from cache if previously loaded ...
... otherwise, each time info is requested, create a Deferred associated with display of the info, that can be resolved/rejected independently of the corresponding jqXHR.