Author: bree Date: Thu Nov 17 08:29:38 2005 New Revision: 1863 Added: azax/branches/json/README.json azax/branches/json/azax/azax_json.js azax/branches/json/azaxresponse_json.py (contents, props changed) azax/branches/json/tests/test_azaxresponse_json.py (contents, props changed) Modified: azax/branches/json/__init__.py azax/branches/json/configure.zcml azax/branches/json/demos/azaxdemo/browser/azax_demo.pt azax/branches/json/demos/azaxdemo/browser/azax_two_select.pt Log: Make azax work with json
This is a first try, and thus it has many things undone. TODO (first): * make a separation of the transport layer - on the js importing level, make this or that js to be imported. This should be totally transparent to the client app. - make the AzaxResponse pluggable * merge the codebases together, hopefully find a good separation line * catch up with newest version To make this work, you must also install the newest version of the jsonserver product. Added: azax/branches/json/README.json ============================================================================== --- (empty file) +++ azax/branches/json/README.json Thu Nov 17 08:29:38 2005 @@ -0,0 +1,26 @@ +Make azax work with json + +This is a first try, and thus it has many things undone. + +TODO (first): + +* make a separation of the transport layer + - on the js importing level, make this or that js to be imported. + This should be totally transparent to the client app. + - make the AzaxResponse pluggable + +* merge the codebases together, hopefully find a good separation line + +* catch up with newest version + +To make this work, you must also install the newest version +of the jsonserver product. + +You can find this in a bazaar (tla) archive in: + + --yet to be added-- + +Homepage of jsonserver: + + http://jsonserver.ree.hu + Modified: azax/branches/json/__init__.py ============================================================================== --- azax/branches/json/__init__.py (original) +++ azax/branches/json/__init__.py Thu Nov 17 08:29:38 2005 @@ -1,3 +1,14 @@ import mimetypes mimetypes.types_map['.azax'] = 'text/xml' + +# json hook +use_json = True + +if use_json: + # This is a terrible hack for rhe moment + # note that in addition there was a change in the five configuration, too + import azaxresponse, azaxresponse_json + azaxresponse.AzaxSelector = azaxresponse_json.AzaxSelector + azaxresponse.AzaxCommand = azaxresponse_json.AzaxCommand + azaxresponse.AzaxResponse = azaxresponse_json.AzaxResponse Added: azax/branches/json/azax/azax_json.js ============================================================================== --- (empty file) +++ azax/branches/json/azax/azax_json.js Thu Nov 17 08:29:38 2005 @@ -0,0 +1,333 @@ +/* azax */ + +function Azax(){}; + +// the results are collected in order to set up the events +// on the new nodes. Should be eventually removed. + +Azax.storedResults = new Array(); + +Azax.storeResults = function(results) +{ +Azax.storedResults[Azax.storedResults.length] = results; +} + +Azax.getLastResults = function() +{ + var length = Azax.storedResults.length; + if (length != 0) + { + return Azax.storedResults[length-1]; + } +} + +Azax.setupStyles = function() +{ + nodes = document.getElementsByTagName("link"); + for (var i=0;i < nodes.length;i++) + { + if (nodes[i].rel=="azax") + { + href = nodes[i].href; + preprocessor = new AzaxPreProcessor(); + domdoc = preprocessor.loadStyleSheet(href); + preprocessor.parseStyleDom(domdoc); + preprocessor.setupEvents(); + } + } +} + + +// preprocessor +function AzaxPreProcessor() { + this.styles = new Array(); +} + +AzaxPreProcessor.prototype.loadStyleSheet = function(uri) +{ + var domDoc = Sarissa.getDomDocument(); + domDoc.async = false; + domDoc.load(uri); + return domDoc; +} + +AzaxPreProcessor.prototype.parseStyleDom = function(domDocument) +{ + styles = domDocument.getElementsByTagName("style"); + + for (var i=0;i < styles.length;i++) + { + var style = styles[i]; + var childNodes = styles[i].childNodes; + selector = ""; + event_ = ""; + action = ""; + + selectorNodes = style.getElementsByTagName("selector"); + if (selectorNodes.length == 1 && selectorNodes[0].childNodes.length != 0) + { + selector = selectorNodes[0].firstChild.nodeValue; + } + + eventNodes = style.getElementsByTagName("event"); + if (eventNodes.length == 1 && eventNodes[0].childNodes.length != 0) + { + event_ = eventNodes[0].firstChild.nodeValue; + } + + actionNodes = style.getElementsByTagName("action"); + if (actionNodes.length == 1 && actionNodes[0].childNodes.length != 0) + { + action = actionNodes[0].firstChild.nodeValue; + } + + if (selector != "" && event_ != "" && action != "") + { + style = new AzaxStyle(selector, event_, action); + this.styles[this.styles.length] = style; + } + } +} + +AzaxStyle = function(selector, event_, action) +{ + this.selector = selector; + this.event = event_; + this.action = action; +} + + +AzaxPreProcessor.prototype.setupEvents = function() +{ + styles = this.styles + for (var y=0;y < styles.length;y++) + { + style = styles[y]; + this.setupEvent(style); + } +} + + +// Code below is really changed in favour of json. +// XXX Refactoring desired! + +var jsonrpc = importModule("jsonrpc"); +Azax.jsonaddr = "."; +Azax.jsonproxy = new jsonrpc.ServiceProxy(Azax.jsonaddr, []); + +AzaxPreProcessor.prototype.setupEvent = function(style) +{ + nodes = cssQuery(style.selector); + for (var y=0; y < nodes.length; y++) + { + node = nodes[y]; + Azax.jsonproxy._addMethodNames([style.action]); + func = function() {Azax.notifyServer(style.action);} + registerEventListener(node, style.event, func); + } +} + +// this did not change + +/* extracted from Plone */ +// cross browser function for registering event handlers +function registerEventListener(elem, event, func) { + if (elem.addEventListener) { + elem.addEventListener(event, func, false); + return true; + } else if (elem.attachEvent) { + var result = elem.attachEvent("on"+event, func); + return result; + } + // maybe we could implement something with an array + return false; +} + +function extractFormsDatas() +{ + forms = document.forms; + var data = new Object(); + + for (var i=0;i < forms.length;i++) + { + var form = forms[i]; + var name = form.name; + if (name) + { + //var f = new Object(); + elements = form.elements; + for (var y=0;y < elements.length;y++) + { + var element = elements[y]; + + if (element.name) + { + //f[element.name] = element.value; + data[element.name] = element.value; + } + } + //data[name] = f; + //for (var p in f){ + // alert("array at position "+p+"="+f[p]); + //} + } + } + return data; +} + +//var domDoc = new XMLHttpRequest(); + +var pythonkw = importModule("pythonkw"); + +Azax.notifyServer = function(action) +{ + // need to lock this so it's serialized + // sending form + form_data = extractFormsDatas(); + //domDoc.open("POST", action, true); + //domDoc.onreadystatechange = Azax.notifyServer_done; + //domDoc.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + //domDoc.send(form_data); + //func(form_data, Azax.notifyServer_done); + + var func = Azax.jsonproxy[action]; + //special marshalling: will become keyword attrs in the request. + //keyword_repr = {"jsonclass": ["zope.kw", form_data]}; + var keyword_repr = new pythonkw.PythonKw(form_data); + func(keyword_repr, Azax.notifyServer_json_done); +} + +Azax.notifyServer_done = function() +{ + // need to lock this so it's serialized + if (domDoc.readyState == 4) + { + Azax.processResult(domDoc.responseXML); + } +} + +Azax.notifyServer_json_done = function(result, err) +{ + if (!err) + { + Azax.processResult(result); + } + else + { + // do something with err + alert(err); + } +} + +var registeredEvents = new Array(); + +Azax.processResult = function(selectors) +{ + for (var key in selectors) + { + var selector = selectors[key]; + var selector_value = selector["value"]; + var commands = selector["commands"]; + + for (var i=0;i < commands.length;i++) + { + var command = commands[i]; + var command_data = command["data"]; + var command_name = command["name"]; + + Azax.executeCommand(selector_value, command_name, command_data); + } + } +} + +Azax.executeCommand = function(selector_value, command_name, command_data) +{ + nodes = cssQuery(selector_value); + for (var i=0;i < nodes.length;i++) + { + var node = nodes[i]; + var results = Azax.commandFunctions[command_name](node, command_data); + Azax.storeResults(results); + } + +} + +// common parts start below here +// only I had to start the fetching. + +var xml = importModule("xml"); + +/* parse and apply azax styles when document is loaded */ +registerEventListener(window, "load", Azax.setupStyles); + +Azax.insertBefore = function(nodeFrom, parentNode, nodeTo) { + var ownerDoc = nodeTo.nodeType == Node.DOCUMENT_NODE ? nodeTo : nodeTo.ownerDocument; + var nodes = nodeFrom.childNodes; + var result = new Array(); + if(ownerDoc.importNode && (!_SARISSA_IS_IE)) { + for(var i=0;i < nodes.length;i++) { + result[i] = parentNode.insertBefore(ownerDoc.importNode(nodes[i], true), nodeTo); + }; + } + else{ + for(var i=0;i < nodes.length;i++) { + result[i] = parentNode.insertBefore(nodes[i].cloneNode(true), nodeTo); + }; + }; + return result; +} + +Azax.commandFunctions = new Array(); +Azax.commandFunctions['setHtmlAsChild']=function(node, command_data) +{ + //var content = document.importNode(command_data['html'], true); + //Sarissa.copyChildNodes(content, node); + + // this would be the way, but innerHTML fails with the select + //node.innerHTML = command_data; + + var oDomDoc = Sarissa.getDomDocument(); + var xmlString = '<root xmlns="http://www.w3.org/1999/xhtml">' + command_data + '</root>'; + + oDomDoc = (new DOMParser()).parseFromString(xmlString, "text/xml"); + var content = document.importNode(oDomDoc, true); + Sarissa.copyChildNodes(content, node); +} + +Azax.commandFunctions['addAfter']=function(node, command_data) +{ + //var content = document.importNode(command_data['html'], true); + var content = document.importNode(command_data, true); + parentNode = node.parentNode; + toNode = node.nextSibling; + inserted = Azax.insertBefore(content, parentNode, toNode); + return inserted; +} + +Azax.commandFunctions['clearChildren']=function(node, command_data) +{ + Sarissa.clearChildNodes(node); +} + +Azax.commandFunctions['copyChildrenFrom']=function(node, command_data) +{ + //var id = command_data['html_id'].firstChild.nodeValue; + var id = command_data; + fromNode = document.getElementById(id); + Sarissa.copyChildNodes(fromNode, node); +} + +Azax.commandFunctions['copyChildrenTo']=function(node, command_data) +{ + //var id = command_data['html_id'].firstChild.nodeValue; + var id = command_data; + toNode = document.getElementById(id); + Sarissa.copyChildNodes(node, toNode); +} + +Azax.commandFunctions['executeCode']=function(node, command_data) +{ + //var code = command_data['code'].firstChild.nodeValue; + var code = command_data['code'].firstChild.nodeValue; + node.eval(code); +} Added: azax/branches/json/azaxresponse_json.py ============================================================================== --- (empty file) +++ azax/branches/json/azaxresponse_json.py Thu Nov 17 08:29:38 2005 @@ -0,0 +1,161 @@ +# -*- coding: ISO-8859-15 -*- +# Copyright (c) 2005 +# Authors: +# Godefroid Chapelle <[EMAIL PROTECTED]> +# Tarek Ziad� <[EMAIL PROTECTED]> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +# 02111-1307, USA. +# +from StringIO import StringIO + +from zope.interface import implements + +from interfaces import IAzaxResponse +#from lxml import etree +# this is not really "doing" it, just needed to test (str repr): +from Products.jsonserver import minjson as json + +class AzaxSelector(dict): + """ azax selector / JSON + + it contains a set of commands related to the selector + """ + + def __init__(self, selectors_ob, selector): + #self.ob = etree.SubElement(selectors_ob, 'selector') + #self.value = etree.SubElement(self.ob, 'value') + #self.commands = etree.SubElement(self.ob, 'commands') + self['commands'] = [] + self['value'] = selector + + def addCommand(self, command): + return AzaxCommand(self['commands'], command) + +class AzaxCommand(dict): + """ azax command / JSON + """ + def __init__(self, commands_ob, name): + #self.ob = etree.SubElement(commands_ob, 'command') + #name_ob = etree.SubElement(self.ob, 'name') + #name_ob.text = name + self['name'] = name + self['data'] = {} + commands_ob.append(self) + + def addData(self, name): + #data = etree.SubElement(self.ob, 'data') + #data.set('name', name) + #return data + + # name is ignored... at the moment + # returns self so we can "add" to it from wrapInHtmlNamespace + return self + + # hack + def _settext(self, text): + self['data'] = text + text = property(lambda: self['data'], _settext) + + +class AzaxResponse(object): + """ azax command Response / JSON + """ + implements(IAzaxResponse) + + def __init__(self, _response=None): + self.selectors = {} + # We don't generate the response now - but we must + # accept the parm as products might (will) supply it + + def __str__(self): + """ renders json + + (not really important, since really the response + is coded from python object transparently but + we can use it for test/debugging) + """ + return json.write(self.selectors) + + def __call__(self): + """ set response content type, if given """ + return self.selectors + + def _getSelector(self, selector): + """ return a selector, given its value tag """ + if self.selectors.has_key(selector): + return self.selectors[selector] + else: + return self._createSelector(selector) + + def _createSelector(self, selector): + """ creates a selector branch """ + result = AzaxSelector(self.selectors, selector) + self.selectors[selector] = result + return result + + def wrapInHtmlNamespace(self, new_value, data): + #new_value = '<junk>' + new_value + '</junk>' + #valuetree = etree.fromstring(new_value.encode('latin1')) + #for elem in valuetree.getchildren(): + # elem.set('xmlns', 'http://www.w3.org/1999/xhtml') + # data.append(elem) + data.text = new_value + + # Common code follows, needs to be refactored + + def setHtmlAsChild(self, selector, new_value): + """ see interfaces.py """ + selector_ob = self._getSelector(selector) + command = selector_ob.addCommand('setHtmlAsChild') + data = command.addData('html') + self.wrapInHtmlNamespace(new_value, data) + + def addAfter(self, selector, new_value): + """ see interfaces.py """ + selector_ob = self._getSelector(selector) + command = selector_ob.addCommand('addAfter') + data = command.addData('html') + self.wrapInHtmlNamespace(new_value, data) + + def clearChildren(self, selector): + """ see interfaces.py """ + selector_ob = self._getSelector(selector) + command = selector_ob.addCommand('clearChildren') + data = command.addData('none') + + def copyChildrenFrom(self, selector, id): + """ see interfaces.py """ + selector_ob = self._getSelector(selector) + command = selector_ob.addCommand('copyChildrenFrom') + data = command.addData('html_id') + data.text = id + + def copyChildrenTo(self, selector, id): + """ see interfaces.py """ + selector_ob = self._getSelector(selector) + command = selector_ob.addCommand('copyChildrenTo') + data = command.addData('html_id') + data.text = id + + def moveChildrenTo(self, selector, id): + """ see interfaces.py """ + self.copyChildrenTo(selector, id) + self.clearChildren(selector) + + def executeCode(self, selector, code): + selector_ob = self._getSelector(selector) + command = selector_ob.addCommand('executeCode') + data = command.addData('code') + data.text = code Modified: azax/branches/json/configure.zcml ============================================================================== --- azax/branches/json/configure.zcml (original) +++ azax/branches/json/configure.zcml Thu Nov 17 08:29:38 2005 @@ -8,7 +8,7 @@ /> <browser:resource - file="azax/azax.js" + file="azax/azax_json.js" name="azax.js" /> Modified: azax/branches/json/demos/azaxdemo/browser/azax_demo.pt ============================================================================== --- azax/branches/json/demos/azaxdemo/browser/azax_demo.pt (original) +++ azax/branches/json/demos/azaxdemo/browser/azax_demo.pt Thu Nov 17 08:29:38 2005 @@ -3,6 +3,10 @@ <link rel="azax" tal:attributes="href string:${context/absolute_url}/++resource++azax_demo.azax"/> <script type="text/javascript" + tal:attributes="src string:${context/absolute_url}/++resource++jsolait.js;" + src="++resource++jsolait.js"> + </script> + <script type="text/javascript" tal:attributes="src string:${context/absolute_url}/++resource++sarissa.js;" src="++resource++sarissa.js"> </script> Modified: azax/branches/json/demos/azaxdemo/browser/azax_two_select.pt ============================================================================== --- azax/branches/json/demos/azaxdemo/browser/azax_two_select.pt (original) +++ azax/branches/json/demos/azaxdemo/browser/azax_two_select.pt Thu Nov 17 08:29:38 2005 @@ -3,6 +3,10 @@ <link rel="azax" tal:attributes="href string:${context/absolute_url}/++resource++azax_two_selects.azax"/> <script type="text/javascript" + tal:attributes="src string:${context/absolute_url}/++resource++jsolait.js;" + src="++resource++jsolait.js"> + </script> + <script type="text/javascript" tal:attributes="src string:${context/absolute_url}/++resource++sarissa.js;" src="++resource++sarissa.js"> </script> Added: azax/branches/json/tests/test_azaxresponse_json.py ============================================================================== --- (empty file) +++ azax/branches/json/tests/test_azaxresponse_json.py Thu Nov 17 08:29:38 2005 @@ -0,0 +1,81 @@ +# -*- coding: ISO-8859-15 -*- +# Copyright (c) 2005 +# Authors: +# Godefroid Chapelle <[EMAIL PROTECTED]> +# Tarek Ziad� <[EMAIL PROTECTED]> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +# 02111-1307, USA. +# +import unittest, os +from zope.testing import doctest +from Testing.ZopeTestCase import ZopeTestCase +from lxml import etree + +from Products.azax.azaxresponse import AzaxResponse + +class AzaxResponseTestCase(ZopeTestCase): + + def test_instanciation(self): + ob = AzaxResponse() + self.assertNotEquals(ob, None) + + def test_str_(self): + ob = AzaxResponse() + self.assertEquals(str(ob), '{}') + + def test__createSelector(self): + ob = AzaxResponse() + selector = ob._createSelector('div.class') + self.assertEquals(str(ob), + '{"div.class": {"commands": [], "value": "div.class"}}') +# '<selectors><selector><value>div.class</value><commands/></selector></selectors>') + +# XXX do we need the equivalent of this? +# self.assertEquals(etree.tostring(selector), +# '<selector><value>div.class</value><commands/></selector>') + + # XXX disabled since there is no updateTag. + def _XXX_test_updateTag(self): + ob = AzaxResponse() + ob.updateTag('div.class', 'new_content') + self.assertEquals(str(ob), + '<selectors><selector><value>div.class</value><commands><command><name>innerHTML</name><data>new_content</data></command></commands></selector></selectors>') + + def test__getSelector(self): + ob = AzaxResponse() + selector = ob._createSelector('div.class') + self.assertEquals(len(ob.selectors), 1) + selector2 = ob._getSelector('div.class') + self.assertEquals(len(ob.selectors), 1) + self.assertEquals(selector, selector2) + + def test_addcommand(self): + ob = AzaxResponse() + ob.setHtmlAsChild('div#demo', '<h1>it worked</h1>') + ob.setHtmlAsChild('div#demo', '<h1>it worked again</h1>') + self.assertEquals(ob(), {'div#demo': + {'commands': [ + {'data': '<h1>it worked</h1>', 'name': 'setHtmlAsChild'}, + {'data': '<h1>it worked again</h1>', 'name': 'setHtmlAsChild'}, + ], + 'value': 'div#demo'} + } + ) + +def test_suite(): + return unittest.TestSuite(( + unittest.makeSuite(AzaxResponseTestCase), + doctest.DocTestSuite('Products.azax.azaxresponse'), + )) -- http://lists.nuxeo.com/mailman/listinfo/z3lab-checkins