looping through DOM / mootools sortables - serialization

I can't seem to get a handle on my list of sortables. They are a list of list elements, each with a
form inside, which I need to get the values from.
Sortables.implement({
serialize: function(){
var serial = [];
this.list.getChildren().each(function(el, i){
serial[i] = el.getProperty('id');
}, this);
return serial;
}
});
var sort = new Sortables('.teams', {
handle: '.drag-handle',
clone: true,
onStart: function(el) {
el.fade('hide');
},
onComplete: function(el) {
//go go gadget go
order = this.serialize();
alert(order);
for(var i=0; i<order.length;i++) {
if (order[i]) {
//alert(order[i].substr(5, order[i].length));
}
}
}
});
the sortables list is then added to a list in a loop with sort.addItems(li); . But when I try to get the sortables outside of the sortables onComplete declaration, js says this.list is undefined.
Approaching the problem from another angle:
Trying to loop through the DOM gives me equally bizarre results. Here are the firebug console results for some code:
var a = document.getElementById('teams').childNodes;
var b = document.getElementById('teams').childNodes.length;
try {
console.log('myVar: ', a);
console.log('myVar.length: ', b);
} catch(e) {
alert("error logging");
}
Hardcoding one li element into the HTML (rather than being injected via JS) changes length == 1, and allows me to access that single element, leading me to believe that accessing injected elements via the DOM is the problem (for this method)
Trying to get the objects with document.getElementById('teams').childNodes[i] returns undefined.
thank you for any help!

not sure why this would fail, i tried it in several ways and it all works
http://www.jsfiddle.net/M7zLG/ test case along with html markup
here is the source that works for local refernece, using the native built-in .serialize method as well as a custom one that walks the dom and gets a custom attribute rel, which can be your DB IDs in their new order (I tend to do that)
var order = []; // global
var sort = new Sortables('.teams', {
handle: '.drag-handle',
clone: true,
onStart: function(el) {
el.fade('hide');
},
onComplete: function(el) {
//go go gadget go
order = this.serialize();
}
});
var mySerialize = function(parentEl) {
var myIds = [];
parentEl.getElements("li").each(function(el) {
myIds.push(el.get("rel"));
});
return myIds;
};
$("saveorder").addEvents({
click: function() {
console.log(sort.serialize());
console.log(order);
console.log(mySerialize($("teams")));
}
});

Related

Interacting with color input with protractor

It's simple to set checkbox or text input value. But how can I set value to input with color type using protractor? I tried to do this:
element(by.id("prop_border-color")).click();
browser.driver.actions()
.sendKeys(protractor.Key.BACK_SPACE)
.sendKeys(protractor.Key.BACK_SPACE)
.sendKeys("00")
.sendKeys(protractor.Key.ENTER)
.perform();
but it triggers this error:
Failed: : Failed to read the 'sessionStorage' property from
'Window': Storage is disabled inside 'data:' URLs.
Is it possible to interact with color picker window somehow?
UPD:
full test:
describe('Panel Editor app', function() {
function addToplevel() {
var elem = element(by.css(".widget-list-item-toplevel"));
var target = element(by.id('droparea'));
browser.driver.actions()
.mouseDown(elem)
.mouseMove(target)
.mouseUp(target)
.perform();
}
function addToToplevel(selector) {
var elem = element(by.css(selector));
var target = element(by.css('.toplevel'));
browser.driver.actions()
.mouseDown(elem)
.mouseMove(target)
.mouseUp(target)
.perform();
}
beforeEach(function() {
browser.get('http://localhost:8080/webapps/panel_editor/index.html');
});
afterEach(function() {
browser.executeScript('window.sessionStorage.clear();');
browser.executeScript('window.localStorage.clear();');
});
it('should check all widgets in toplevel', function() {
addToplevel();
addToToplevel(".widget-list-item-rows");
browser.sleep(300);
element(by.model("dialogCtrl.dialogs.widget.widget_model.props[q].value")).clear().sendKeys(4);
element(by.id("widget_modal")).element(by.buttonText("OK")).click();
browser.sleep(300);
element.all(by.css(".builder-rows > div")).then(function(rows) {
for (var i = 0, l = rows.length-1; i < l; i++) {
rows[i].getCssValue("border-color").then(function(val) {
expect(val == "rgb(221, 221, 221)").toBe(true);
})
}
});
element(by.id("prop_border-color")).click();
// Color picker shows.
browser.driver.actions()
.sendKeys(protractor.Key.BACK_SPACE)
.sendKeys(protractor.Key.BACK_SPACE)
.sendKeys("00")
.sendKeys(protractor.Key.ENTER)
.perform();
// ERROR HERE
element.all(by.css(".builder-rows > div")).then(function(rows) {
for (var i = 0, l = rows.length-1; i < l; i++) {
rows[i].getCssValue("border-color").then(function(val) {
expect(val == "rgb(221, 221, 0)").toBe(true);
})
}
});
});
});
UPD2:
I temporarily solved this problem by using executeScript method and setting value directly from js:
browser.executeScript("$('#prop_border-color').val('#FF0000'); $('#prop_border-color').change();");
But still looking for better solution
I suspect the backspaces are not sent to the color input, but instead make the browser go back in the browser history which leads to a blank page and a local storage access error.
Instead, resolve the click promise explicitly, use clear() to clear the input field and send the keys:
var colorInput = element(by.id("prop_border-color"));
colorInput.click().then(function () {
colorInput.clear();
colorInput.sendKeys("#FF0000");
});
Another approach to try would be to replace browser.driver with browser when calling the actions().

Dojo _TemplatedMixin: changing templateString

How do I change the template of a widget, using mixins dijit/_TemplatedMixin and dijit/_WidgetsInTemplateMixin, at a later time (not in the constructor)?
My scenario is that the widget must make a call to the server to get data, and the callback function will then merge the data with a template file and then the resulting template should be used for the templateString. The widget should update its contents at this point.
Setting the templateString and calling buildRendering() has no effect.
Here is a simplified version of my code:
define([
"dojo/_base/declare",
"dojo/_base/lang",
"dijit/_WidgetBase",
"dijit/_TemplatedMixin",
"dijit/_WidgetsInTemplateMixin",
],
function(declare, lang, _WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin) {
return declare([_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], {
constructor: function(id) {
this.id = id;
this.templateString = "<div>Loading...</div>";
//use xhr to call REST service to get data.
//dataLoadedCallback is executed with response data
...
},
dataLoadedCallback : function(data) {
this.destroyRendering();
//render a templateString using the data response from the rest call
this.templateString = "<div>Data is loaded. Name:" + data.name + "</div>"
this.buildRendering();
},
});
});
You cannot do such thing. The template is parsed only once before postCreate method.
However there is few things you can do:
Create a non-ui widget which will do the XHR call. When this non-ui widget get the XHR response it creates the UI widget with the correct templateString
Or use dojo/dom-construct. It contains a toDom method which you can use for converting your string into nodes. Then you can append that to the widget.
Note: this will not parse any data-dojo attributes
You could also directly inject the received templateString into the widget domNode:
dataLoadedCallback : function(data) {
this.domNode.innerHTML = "<div>Data is loaded. Name:" + data.name + "</div>";
//you might be able to parse the content (if you have subwidgets) using dojo/parse
},
Last but not least, here is a util I wrote for my self. It allow to parse any templateString at any time (like dojo does on widget creation)
define([
'dojo/dom-construct',
'dojo/string',
'dijit/_AttachMixin',
'dijit/_TemplatedMixin'
], function(domConstruct, string,
_AttachMixin, _TemplatedMixin) {
// summary:
// provide an utility to parse a template a runtime (and create attach point, atach events, etc...)
// Copyright: Benjamin Santalucia
var GET_ATTRIBUTE_FUNCTION = function(n, p) { return n.getAttribute(p); },
_TemplateParserMixin = function() {};
_TemplateParserMixin.prototype = {
parseTemplate: function(template, data, container, position, transformer) {
// summary:
// parse the template exactly as dojo will nativly do with a templateString
if(this._attachPoints === undefined) {
this._attachPoints = [];
}
if(this._attachEvents === undefined) {
this._attachEvents = [];
}
var nodes,
x,
len,
newTemplate = string.substitute(template, data, transformer),
node = domConstruct.toDom(_TemplatedMixin.prototype._stringRepl.call(this, newTemplate));
if(node.nodeName === '#document-fragment') {
node = node.firstChild;
}
//parse all nodes and create attach points and attach events
nodes = node.getElementsByTagName('*');
len = nodes.length;
for(x = -1; x < len; x++) {
_AttachMixin.prototype._processTemplateNode.call(this, x < 0 ? node : nodes[x], GET_ATTRIBUTE_FUNCTION, _AttachMixin.prototype._attach);
}
if(container) {
domConstruct.place(node, container, position);
}
return node;
}
};
return _TemplateParserMixin;
});
Usage is:
returnedNode = w.parseTemplate(newTemplateString, {
templatePlaceHolderName: 'foo' //for teplate with placeholders like ${templatePlaceHolderName}
}, domNodeToInsertIn, 'only'); //last parameter is same as dojo/dom-construct::place() >> last, first, before, after, only

Can I detect changes in a node's markup text using dojo?

I have a bunch of nodes that will contain markup in an unpredictable structure. I want to be able to watch these nodes and see if the html of the any of the child nodes or their descendants change, no matter how slightly. If they do, then I want to fire an event.
Can I do this through dojo? I'm using 1.10, the latest one.
Thanks.
It sounds like you're looking for dom mutations. As far as I'm aware dojo does not provide an api for this, but they're pretty simple to set up. The problem is different browsers have different ways of doing this.
var observeNode = document.getElementById('observeMe');
// Check for vendor-specific versions of MutationObserver.
MutationObserver = (function() {
var prefixes = ['WebKit', 'Moz', 'O', 'Ms', ''];
for (var i=0, il=prefixes.length; i<il; i++) {
if (prefixes[i] + 'MutationObserver' in window) {
return window[prefixes[i] + 'MutationObserver'];
}
}
}());
// Sniff for MutationObserver support
if (MutationObserver) {
var observer = new MutationObserver(function(mutations) {
alert('Something changed!');
});
observer.observe(observeNode, {attributes: true, childList: true, characterData: true});
} else {
// Fall back to mutation events
if (observeNode.addEventListener) {
observeNode.addEventListener('DOMSubtreeModified', function() {
alert('Something changed!');
});
}
// IE8 and below has its own little weird thing
else {
observeNode.onpropertychange = function() {
alert('Something Changed!');
}
}
}

different ways of assigning onclick events dojo

The second approach, where I hardcode the input id's and connect them to onclick events works properly.
But, when I use the first approach, it doesn't work.
The code executes in this manner.
select1.on('change',function(evt) {
requiredFunction(select8.id);//select9 is not present (so I changed loop end value from inputs.length -1 to inputs.length -2 )
}
Am I missing some event handling principles in dojo?
Approach1:
function assignOnClickEvents(table) {
var inputs = document.getElementById(table).getElementsByClassName('classname');
for (var i = 0; i < (inputs.length - 1); i++) {
dijit.byId(inputs[i].id).on('change', function (evt) {
requiredFunction(inputs[i+1].id);
});
}
}
Approach2:
function assignOnClickEvents() {
var select1 = dijit.byId('select1');
var select2 = dijit.byId('select2');
var select3 = dijit.byId('select3');
var select4 = dijit.byId('select4');
var select5 = dijit.byId('select5');
var select6 = dijit.byId('select6');
var select7 = dijit.byId('select7');
var select8 = dijit.byId('select8');
var select9 = dijit.byId('select9');
select1.on('change', function (evt) {
requiredFunction('select2');
});
select2.on('change', function (evt) {
requiredFunction('select3');
});
select3.on('change', function (evt) {
requiredFunction('select4');
});
select4.on('change', function (evt) {
requiredFunction('select5');
});
select5.on('change', function (evt) {
requiredFunction('select6');
});
select6.on('change', function (evt) {
requiredFunction('select7');
});
select7.on('change', function (evt) {
requiredFunction('select8');
});
select8.on('change', function (evt) {
requiredFunction('select9');
});
}
You're mixing DOM node IDs and Dijit IDs. This could be a possible reason why your code isn't working.
To fix this, you could try the following approach:
var inputs = dijit.findWidgets(table); // Returns widgets, not DOM nodes
for(var i = 0;i < inputs.length - 1;i++) {
inputs[i].on('change', function(evt) {
// Remind: this returns the widget ID, not the DOM ID
requiredFunction(inputs[i+1].id);
});
}
In dojo there is a difference between widgets and DOM nodes. So using DOM functions (to retrieve a DOM node by ID or by classname) will not always work. They could work, but that's not always the fact.
You can also call your function requiredFunction() as follow :
<input data-dojo-attach-event="onChange:requiredFunction"></input>
This will reduce your time of looping and work similar as you want.
All the best.

Is it possible to filter data in a dgrid like you can in a datagrid? If so, how?

I'm relatively new to dojo and have seen how datagrid offers a dynamic filtering capability that reduces the visible rows based on what you type into a filter text input. I have not found any examples of how to do it with the dgrid. If it can be done, please provide an example or point me to a resource that offers a tutorial or example. Thanks!
Yes, it is possible. Use dgrid/OnDemandGrid and define query function that will return true or false based on your logic for each row in dojo/store powering the grid.
I prepared an example to play with at jsFiddle: http://jsfiddle.net/phusick/7gnFd/, so I do not have to explain too much:
The Query Function:
var filterQuery = function(item, index, items) {
var filterString = filter ? filter.get("value") + "" : "";
// early exists
if (filterString.length < 2) return true;
if (!item.Name) return false;
// compare
var name = (item.Name + "").toLowerCase();
if (~name.indexOf(filterString.toLowerCase())) { return true;}
return false;
};
The Grid:
var grid = new Grid({
store: store,
query: filterQuery, // <== the query function for filtering
columns: {
Name: "Name",
Year: "Year",
Artist: "Artist",
Album: "Album",
Genre: "Genre"
}
}, "grid");
I know this isn't the answer to the question asked, and the answer provided is masterful and we use it quite a bit.
However, this functionality doesn't seem to work well at all if you're using a TreeGrid (columns with the "dgrid/tree" plugin). I've written some code to simulate the same behavior as the accepted answer with a tree grid. It's basically just looping through the items in the store and hiding any row elements that don't match whatever condition you set forth. Thought I would share it in case it helps anybody. It's rather ugly and I'm sure it can be improved upon, but it works.
It basically uses the same concept as phusick's answer. You need to watch a value on a TextBox, but instead of refreshing the grid you have it call a function:
textBox.watch("value", lang.hitch(this, function() {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
};
timeoutId = setTimeout(lang.hitch(this, function() {
this.filterGridByName(textBox.get('value'), myGrid);
}, 300));
}));
And here's the function:
filterGridByName: function(name, grid){
try {
for (var j in grid.store.data){
var dataItem = grid.store.data[j];
var childrenLength = dataItem.children.length;
var childrenHiddenCount = 0;
var parentRow = grid.row(dataItem.id);
for (var k in dataItem.children){
var row = grid.row(dataItem.children[k].id);
var found = false;
if (dataItem.children[k].name.toLowerCase().indexOf(name.toLowerCase()) != -1){
found = true;
}
if (found){
if (row.element){
domStyle.set(row.element, "display", "block");
}
if (parentRow.element){
domStyle.set(parentRow.element, "display", "block");
}
} else {
childrenHiddenCount++;
// programmatically uncheck any hidden item so hidden items
for (var m in grid.dirty){
if (m === dataItem.children[k].id && grid.dirty[m].selected){
grid.dirty[m].selected = false;
}
}
if (row.element){
domStyle.set(row.element, "display", "none");
}
}
}
// if all of the children were hidden, hide the parent too
if (childrenLength === childrenHiddenCount){
domStyle.set(parentRow.element, "display", "none");
}
}
} catch (err){
console.info("error: ", err);
}
}