jquery Syncing dom element values - javascript

I have a DOM element like this:
<div id='master-value'>Apples</div>
I have many other elements elsewhere on the page that I need to sync with the 'master-value'.
<div class='fruit-value' data-reference='master-value'>Apples</div>
<div class='some-fruit' data-reference='master-value'>Apples</div>
When I change the value of the 'master-value', I want all the synced elements to update with it.
$('#master-value').text('Pears');
Should affect:
<div class='fruit-value' data-reference='master-value'>Pears</div>
<div class='some-fruit' data-reference='master-value'>Pears</div>
What I don't want, is on every change of 'master-value' to have to search through all the elements in order to find the synced elements in order to change them. I think that's quite slow when there are many elements that needs to be searched through.
There should be some way for the child values to be pre-bound to the master value so that the selection goes quickly.
$('.fruit-value, .some-fruit').sync('#master-value');
I have some ideas, for instance: I can create an array of preselected synced objects, bind a custom event on the master value and run that event whenever I change the value. The event would go through the array to update all the child elements.
I'm sure there's a better way of doing it though...
Thanks!

You can store the selector once, like this:
var elements = $('.fruit-value, .some-fruit'); //do this once
elements.text($("#master-value").text()); //when you want to sync
The elements variable/jQuery object will keep an array of references to DOM elements so it won't be traversing to find them each time.

wouldn't it be easier to give them all the same class?
So you coud do
$('.fruit').text('Pears')

If you're looking for plugin type of functionality, try this:
When setting up, it takes an object with one property syncWith to set up the elements it should sync with.
When setting the text, it will set the text for the master and the synced elements.
Try it out: http://jsfiddle.net/GH33J/
Just a first attempt. There would be room for improvement if (for example) the master was more than one element. There should be a global reference to all the elements to synchronize and an option to tell if the masters should be synced too.
$.fn.sync = function(arg) {
// if arg plain object, we are doing an initial setup
if ($.isPlainObject(arg)) {
return this.each(function() {
$.data(this, 'syncWith', $(arg.syncWith));
});
// if arg is jQuery object, we are adding new items
} else if (arg.jquery) {
return this.each(function() {
var $set = $.data(this, 'syncWith');
$.each(arg, function() {
$set.push(this);
});
});
console.log(this.data('syncWith'));
// otherwise assume we have a string, and are syncing a new value
} else {
return this.each(function() {
$(this).text(arg);
$.data(this, 'syncWith').text(arg);
});
}
};
// Set up the sync
$('#master-value').sync({
syncWith: '.fruit-value,.some-fruit'
});
var $new = $('<div class="fruit-value">Apples</div>').appendTo('body');
// Pass a jQuery object containing newly created element(s) to add to the set
$('#master-value').sync($new);
// Activate a sync
$('#master-value').sync("pears");​

OK here we go:
This is the official data linking plugin from Microsoft. It's now being supported by the jQuery Core team, so we know it's good. :)
http://weblogs.asp.net/scottgu/archive/2010/05/07/jquery-templates-and-data-linking-and-microsoft-contributing-to-jquery.aspx
http://blog.jquery.com/2010/10/04/new-official-jquery-plugins-provide-templating-data-linking-and-globalization/

Related

Can't access children of Konva Stage after cloning

I have a problem with konvajs. I have a konva Stage that I clone into a temporary Stage, so I can revert changes made by a user, when the user cancels.
The way I do this is, that I clone the existing Stage into a temporary one, destroy the children of the origin and after that I move the children of the temporary Stage back to the original and destroy the temporary Stage. The problem is, when I try to access the children now, for example via findOne('#id-of-child'), I get undefined, even though the children exist.
Here's what I've done so far:
clone: function()
{
var cloned_stage = this.stage.clone();
Array.each(this.stage.getChildren(), function(layer, lidx) {
if (layer.attrs.id) {
// setting the id to the ones, the children had before
cloned_stage.children[lidx].attrs.id = layer.attrs.id;
}
});
return cloned_stage;
},
restore: function(tmp_stage)
{
this.stage.destroyChildren();
Array.each(tmp_stage.getChildren(), function(layer, lidx) {
var tmp_layer = layer.clone();
tmp_layer.attrs.id = layer.attrs.id;
tmp_layer.moveTo(this.stage);
}.bind(this));
tmp_stage.destroy();
this.stage.draw();
},
Now when the user opens the toolbox to change something, the current stage is cloned with the "clone" function and when the user cancels his changes, the "restore" function is called with the cloned stage as parameter.
But after that when I try to do things like the following they do not work as expected.
some_child_of_the_stage.getLayer(); -> returns null
var edit_layer = this.stage.findOne('#edit-layer'); -> returns undefined
But the "some_child_of_the_stage" does exist and has a layer of course and the stage has a child with the id "edit-layer".
Me and my colleague are at our wit's end.
I appreciate any help and suggestions thank you.
Update:
A short fiddle showing the problem via console.log:
fiddle
It is better not to touch attrs property of a node and use public getters and setters.
Konva has special logic for storing id property. Selector by id #edit-layer may not work because of direct access to attrs id.
You can use name property fo that case.
https://jsfiddle.net/s36hepvg/12/

How to update cached jquery object after adding elements via AJAX

I'm trying to write a plugin-like function in jQuery to add elements to a container with AJAX.
It looks like this:
$.fn.cacheload = function(index) {
var $this = $(this);
$.get("cache.php", {{ id: index }).done(function(data) {
// cache.php returns <div class='entry'>Content</div> ...
$(data).insertAfter($this.last());
});
}
and I would like to use it like this:
var entries = $("div.entry"),
id = 28;
entries.cacheload(id);
Think that this would load another "entry"-container and add it to the DOM.
This is works so far. But of course the variable that holds the cached jQuery object (entries) isn't updated. So if there were two divs in the beginning and you would add another with this function it would show in the DOM, but entries would still reference the original two divs only.
I know you can't use the return value of get because the AJAX-call is asynchronous. But is there any way to update the cached object so it contains the elements loaded via AJAX as well?
I know I could do it like this and re-query after inserting:
$.get("cache.php", {{ id: num }).done(function(data) {
$(data).insertAfter($this.last());
entries = $("div.entry");
});
but for this I would have to reference the variable holding the cached objects directly.
Is there any way around this so the function is self-contained?
I tried re-assigning $(this), but got an error. .add() doesn't update the cached object, it creates a new (temporary) object.
Thanks a lot!
// UPDATE:
John S gave a really good answer below. However, I ended up realizing that for me something else would actually work better.
Now the plugin function inserts a blank element (synchronously) and when the AJAX call is complete the attributes of that element are updated. That also ensures that elements are loaded in the correct order. For anyone stumbling over this, here is a JSFiddle: http://jsfiddle.net/JZsLt/2/
As you said yourself, the ajax call is asynchronous. Therefore, your plugin is asynchronous as as well. There's no way for your plugin to add the new elements to the jQuery object until the ajax call returns. Plus, as you discovered, you can't really add to the original jQuery object, you can only create a new jQuery object.
What you can do is have the plugin take a callback function as a second parameter. The callback could be passed a jQuery object that contains the original elements plus the newly inserted ones.
$.fn.cacheload = function(index, callback) {
var $this = this;
$.get('cache.php', { id: index }).done(function(html) {
var $elements = $(html);
$this.last().after($elements);
if (callback) {
callback.call($this, $this.add($elements));
}
});
return $this;
};
Then you could call:
entries.cacheload(id, function($newEntries) { doSomething($newEntries); } );
Of course, you could do this:
entries.cacheload(id, function($newEntries) { entries = $newEntries; } );
But entries will not be changed until the ajax call returns, so I don't see much value in it.
BTW: this inside a plugin refers to a jQuery object, so there's no need to call $(this).

JS vs DOM timing: .remove() element visually happens, but travesal still includes it

The short description of the functionality that we are trying to achieve: we have a list of source objects on the left, a person can drag new items from the list to a list on the right, items thus get added to the list on the right; they can also remove items from the list on the right. The list on the right then gets saved whenever it is changed. (I don't think the specifics of how/where it is being saved matter...)
I am having a problem with a bit of timing in the JavaScript vs. DOM elements realm of things. Items that are already on the list on the right can be removed. We have some code that fires on a 'remove/delete' type icon/button on a DOM element, that is supposed to remove the element from the DOM visually and permanently (i.e. it doesn't need to be brought back with a 'show'). This visual change should then also show up in the JSON object that is built when the JS traverses the DOM tree to build the new updated list.
However, this chunk of JS code that runs immediately after this .remove() is called, the element that should have just been removed still shows up in the JSON object. This is not good.
Here are what I believe to be the relevant bits of code operating here. This lives in a web browser; much of this is in the document.ready() function. A given list can also have subsections, hence the sub-list parts and loops.
The on-click definition:
$('body').on('click', '.removeLine', function() {
var parent=$(this).parent().parent().parent(); //The button is a few DIVs shy of the outer container
var List=$(this).closest('article'); //Another parent object, containing all the
parent.fadeOut( 300,
function() {
parent.slideUp(300);
parent.remove();
}
);
sendList(List); // This builds and stores the list based on the DOM elements
});
And then later on, this function definition:
function sendList(List) {
var ListArray=[],
subListArray=[],
itemsArray = [],
subListName = "";
var ListTitle = encodeText(List.find('.title').html());
// loop through the subLists
List.find('.subList').each(
function(index, element) {
subListName=($(this).find('header > .title').html()); // Get sublist Title
subListID=($(this).attr('id')); // Get subList ID
// loop through the line items
itemsArray=[];
$(this).find('.itemSearchResult').each(
function(index, element) {
// Build item Array
if( $(this).attr('data-itemid')!= item ) {
itemArray.push( $(this).attr('data-itemid'));
}
}
);
// Build SubList Array with items Array
subListArray.push(
{
"subListName": subListName,
"subListID" : subListID,
"items" : itemsArray
}
);
}
); <!-- end SubList Loop -->
// Complete List Array with subListArray
ListArray ={
"ListName": ListTitle,
"ListID": List.attr('id'),
"subLists": subListArray
};
// Send New List to DataLists Object - the local version of storage
updateDataLists(ListArray);
// Update remote storage
window.location= URLstring + "&$Type=List" + "&$JSON=" + JSON.stringify(ListArray) + "&$objectID=" + ListArray.ListID;
};
It seems to be the interaction of the 'parent.remove()' step and then the call to 'sendList()' that get their wires crossed. Visually, the object on screen looks right, but if we check the data being sent to the storage, it comes through WITH the object that was visually removed.
Thanks,
J
PS. As you can probably tell, we are new at the Javascript thing, so our code may not be terribly efficient or proper. But...it works! (Well, except for this issue. And we have run into this issue a few times. We have a workaround for it, but I would rather understand what is going on here. Learn the deeper workings of JS so we don't create these problems in the first place.)
There's a few things going on here, but I'm going to explain it by approaching it from an asynchronous programming perspective.
You are calling sendList before the element gets removed from the DOM. Your element doesn't get removed from the DOM until after your fadeOut callback gets executed (which takes 300ms).
Your sendList function gets called immediately after you begin the fadeOut, but your program doesn't wait to call sendList until your fadeOut is finished - that's what the callback is for.
So I would approach it by calling sendList in the callback, after your DOM element has been removed like this:
$('body').on('click', '.removeLine', function() {
var el = $(this); //maintain a reference to $(this) to use in the callback
var parent=$(this).parent().parent().parent(); //The button is a few DIVs shy of the outer container
parent.fadeOut( 300,
function() {
parent.slideUp(300);
parent.remove();
sendList(el.closest('article'));
}
);
});

New Element in MooTools by UID

Is it possible to instantiate an element on Mootools based on the automatic UID that mootools create?
EDIT: To give more info on what is going. I'm using https://github.com/browserstate/history.js to make a history within an ajax page. When I add a DOM element to it (which does not have an id), at some point it passes through a JSON.toString methods and what I have of the element now is just the uid.
I need to recreate the element based on this UID, how could I go about doing that? Do I need to first add it to the global storage to retrieve later? If so, how?
in view of edited question:
sorry, I fail to understand what you are doing.
you have an element. at some point the element is turned into an object that gets serialised (all of it? prototypes etc?). you then take that data and convert to an object again but want to preserve the uid? why?
I don't understand how the uid matters much here...
Using global browser storage also serialises to string so that won't help much. Are we talking survival of page loads here or just attach/detach/overwrite elements? If the latter, this can work with some tweaking.
(function() {
var Storage = {};
Element.implement({
saveElement: function() {
var uid = document.id(this).uid;
Storage[uid] = this;
return this;
}
});
this.restoreElement = function(uid) {
return Storage[uid] || null;
}
})();
var foo = document.id("foo"), uid = foo.uid;
console.log(uid);
foo.saveElement().addEvent("mouseenter", function() { alert("hi"); } );
document.id("container").set("html", "");
setTimeout(function() {
var newElement = restoreElement(uid);
if (newElement)
newElement.inject(document.body);
console.log(newElement.uid);
}, 2000);
http://jsfiddle.net/dimitar/7mwmu/1/
this will allow you to remove an element and restore it later.
keep in mind that i do container.set("html", ""); which is not a great practice.
if you do .empty(), it will GC the foo and it will wipe it's storage so the event won't survive. same for foo.destroy() - you can 'visually' restore the element but nothing linked to it will work (events or fx).
you can get around that by using event delegation, however.
also, you may want to store parent node etc so you can put it back to its previous place.

Dojo extending dojo.dnd.Source, move not happening. Ideas?

NOTICE: THIS IS SOLVED, I WILL PUBLISH THE SOLUTION HERE ASAP.
Hey all,
Ok... I have a simple dojo page with the bare essentials. Three UL's with some LI's in them. The idea si to allow drag-n-drop among them but if any UL goes empty due to the last item being dragged out, I will put up a message to the user to gie them some instructions.
In order to do that, I wanted to extend the dojo.dnd.Source dijit and add some intelligence. It seemed easy enough. To keep things simple (I am loading Dojo from a CDN) I am simply declating my extension as opposed to doing full on module load. The declaration function is here...
function declare_mockupSmartDndUl(){
dojo.require("dojo.dnd.Source");
dojo.provide("mockup.SmartDndUl");
dojo.declare("mockup.SmartDndUl", dojo.dnd.Source, {
markupFactory: function(params, node){
//params._skipStartup = true;
return new mockup.SmartDndUl(node, params);
},
onDndDrop: function(source, nodes, copy){
console.debug('onDndDrop!');
if(this == source){
// reordering items
console.debug('moving items from us');
// DO SOMETHING HERE
}else{
// moving items to us
console.debug('moving items to us');
// DO SOMETHING HERE
}
console.debug('this = ' + this );
console.debug('source = ' + source );
console.debug('nodes = ' + nodes);
console.debug('copy = ' + copy);
return dojo.dnd.Source.prototype.onDndDrop.call(this, source, nodes, copy);
}
});
}
I have a init function to use this to decorate the lists...
dojo.addOnLoad(function(){
declare_mockupSmartDndUl();
if(dojo.byId('list1')){
//new mockup.SmartDndUl(dojo.byId('list1'));
new dojo.dnd.Source(dojo.byId('list1'));
}
if(dojo.byId('list2')){
new mockup.SmartDndUl(dojo.byId('list2'));
//new dojo.dnd.Source(dojo.byId('list2'));
}
if(dojo.byId('list3')){
new mockup.SmartDndUl(dojo.byId('list3'));
//new dojo.dnd.Source(dojo.byId('list3'));
}
});
It is fine as far as it goes, you will notice I left "list1" as a standard dojo dnd source for testing.
The problem is this - list1 will happily accept items from lists 2 & 3 who will move or copy as apprriate. However lists 2 & 3 refuce to accept items from list1. It is as if the DND operation is being cancelled, but the debugger does show the dojo.dnd.Source.prototype.onDndDrop.call happening, and the paramaters do look ok to me.
Now, the documentation here is really weak, so the example I took some of this from may be way out of date (I am using 1.4).
Can anyone fill me in on what might be the issue with my extension dijit?
Thanks!
If you use Dojo XD loader (used with CDNs), all dojo.require() are asynchronous. Yet declare_mockupSmartDndUl() assumes that as soon as it requires dojo.dnd.Source it is available. Generally it is not guaranteed.
Another nitpicking: dojo.dnd.Source is not a widget/dijit, while it is scriptable and can be used with the Dojo Markup, it doesn't implement any Dijit's interfaces.
Now the problem — the method you are overriding has following definition in 1.4:
onDndDrop: function(source, nodes, copy, target){
// summary:
// topic event processor for /dnd/drop, called to finish the DnD operation
// source: Object
// the source which provides items
// nodes: Array
// the list of transferred items
// copy: Boolean
// copy items, if true, move items otherwise
// target: Object
// the target which accepts items
if(this == target){
// this one is for us => move nodes!
this.onDrop(source, nodes, copy);
}
this.onDndCancel();
},
Notice that it has 4 arguments, not 3. As you can see if you do not pass the 4th argument, onDrop is never going to be called by the parent method.
Fix these two problems and most probably you'll get what you want.
In the end, I hit the Dojo IRC (great folks!) and we ended up (so far) with this...
function declare_mockupSmartDndUl(){
dojo.require("dojo.dnd.Source");
dojo.provide("mockup.SmartDndUl");
dojo.declare("mockup.SmartDndUl", dojo.dnd.Source, {
markupFactory: function(params, node){
//params._skipStartup = true;
return new mockup.SmartDndUl(node, params);
},
onDropExternal: function(source, nodes, copy){
console.debug('onDropExternal called...');
// dojo.destroy(this.getAllNodes().query(".dndInstructions"));
this.inherited(arguments);
var x = source.getAllNodes().length;
if( x == 0 ){
newnode = document.createElement('li');
newnode.innerHTML = "You can drag stuff here!";
dojo.addClass(newnode,"dndInstructions");
source.node.appendChild(newnode);
}
return true;
// return dojo.dnd.Source.prototype.onDropExternal.call(this, source, nodes, copy);
}
});
}
And you can see where I am heading, I put in a message when the source is empty (client specs, ug!) and I need to find a way to kill it when something gets dragged in (since it is not, by definition, empty any more ona incomming drag!). That part isnt workign so well.
Anyway, the magic was not to use the onDnd_____ functions, but the higher level one and then call this.inherited(arguments) to fire off the built in functionality.
Thanks!
dojo.require("dojo.dnd.Source");
dojo.provide("mockup.SmartDndUl");
dojo.declare("mockup.SmartDndUl", dojo.dnd.Source, {
Dojo require statement and declare statement are next to next. I think that will cause dependencies problem.
the dojo require statement should go outside onload block and the declare statement should be in onload block.

Categories