[Jifty-commit] r3446 - in jifty/branches/trimclient: .
examples/Yada/etc examples/Yada/lib examples/Yada/lib/Yada
examples/Yada/lib/Yada/View examples/Yada/share/web/static/js
examples/Yada/share/web/static/js/Asynapse
lib/Jifty/View/Declare lib/Jifty/Web share/web/static/js
jifty-commit at lists.jifty.org
jifty-commit at lists.jifty.org
Mon Jun 11 17:57:20 EDT 2007
Author: clkao
Date: Mon Jun 11 17:57:19 2007
New Revision: 3446
Added:
jifty/branches/trimclient/examples/Yada/lib/Yada.pm
jifty/branches/trimclient/examples/Yada/share/web/static/js/
jifty/branches/trimclient/examples/Yada/share/web/static/js/Asynapse/
jifty/branches/trimclient/examples/Yada/share/web/static/js/Asynapse/REST.js
jifty/branches/trimclient/examples/Yada/share/web/static/js/trimpath-template.js
Modified:
jifty/branches/trimclient/ (props changed)
jifty/branches/trimclient/examples/Yada/etc/config.yml
jifty/branches/trimclient/examples/Yada/lib/Yada/View.pm
jifty/branches/trimclient/examples/Yada/lib/Yada/View/Todo.pm
jifty/branches/trimclient/lib/Jifty/View/Declare/CRUD.pm
jifty/branches/trimclient/lib/Jifty/Web/PageRegion.pm
jifty/branches/trimclient/share/web/static/js/jifty.js
jifty/branches/trimclient/share/web/templates/__jifty/webservices/xml
Log:
Proof of concept.
Modified: jifty/branches/trimclient/examples/Yada/etc/config.yml
==============================================================================
--- jifty/branches/trimclient/examples/Yada/etc/config.yml (original)
+++ jifty/branches/trimclient/examples/Yada/etc/config.yml Mon Jun 11 17:57:19 2007
@@ -16,7 +16,7 @@
RecordBaseClass: Jifty::DBI::Record::Cachable
User: ''
Version: 0.0.1
- DevelMode: 0
+ DevelMode: 1
L10N:
PoDir: share/po
LogLevel: INFO
Added: jifty/branches/trimclient/examples/Yada/lib/Yada.pm
==============================================================================
--- (empty file)
+++ jifty/branches/trimclient/examples/Yada/lib/Yada.pm Mon Jun 11 17:57:19 2007
@@ -0,0 +1,6 @@
+package Yada;
+
+
+Jifty->web->add_javascript(qw( Asynapse/REST.js trimpath-template.js ) );
+
+1;
Modified: jifty/branches/trimclient/examples/Yada/lib/Yada/View.pm
==============================================================================
--- jifty/branches/trimclient/examples/Yada/lib/Yada/View.pm (original)
+++ jifty/branches/trimclient/examples/Yada/lib/Yada/View.pm Mon Jun 11 17:57:19 2007
@@ -1,18 +1,31 @@
package Yada::View;
use Jifty::View::Declare -base;
+use Jifty::View::Declare::CRUD;
+for (qw/todo/) {
+ Jifty::View::Declare::CRUD->mount_view($_);
+}
+
template 'index.html' => page {
my $self = shift;
title { _('Yada!') };
+ hyperlink(label => 'clear',
+ onclick => [{region => 'test_region',
+ replace_with => '__jifty/empty',
+ }]);
+
+ render_region(
+ name => 'test_region',
+ path => '/__jifty/empty'
+ );
+
+
+
form {
+ set(item_path => '/todo/view_brief');
render_region(name => 'list', path => '/todo/list');
}
};
-use Jifty::View::Declare::CRUD;
-for (qw/todo/) {
- Jifty::View::Declare::CRUD->mount_view($_);
-}
-
1;
Modified: jifty/branches/trimclient/examples/Yada/lib/Yada/View/Todo.pm
==============================================================================
--- jifty/branches/trimclient/examples/Yada/lib/Yada/View/Todo.pm (original)
+++ jifty/branches/trimclient/examples/Yada/lib/Yada/View/Todo.pm Mon Jun 11 17:57:19 2007
@@ -1,6 +1,21 @@
package Yada::View::Todo;
use strict;
-use Jifty::View::Declare -base;
use base 'Jifty::View::Declare::CRUD';
+use Jifty::View::Declare -base;
+
+template 'view_brief' => sub {
+ my $self = shift;
+ my ( $object_type, $id ) = ( $self->object_type, get('id') );
+ my $record = $self->get_record($id);
+
+ div { {class is "description" };
+ outs($record->description);
+ hyperlink(label => 'details',
+ onclick => [{region => 'test_region',
+ replace_with => $self->fragment_for('view'),
+ args => { id => $id },
+ }]);
+ };
+};
1;
Added: jifty/branches/trimclient/examples/Yada/share/web/static/js/Asynapse/REST.js
==============================================================================
--- (empty file)
+++ jifty/branches/trimclient/examples/Yada/share/web/static/js/Asynapse/REST.js Mon Jun 11 17:57:19 2007
@@ -0,0 +1,285 @@
+if ( typeof Asynapse == 'undefined' ) {
+ Asynapse = {}
+}
+
+if ( typeof Asynapse.REST == 'undefined' ) {
+ Asynapse.REST = {}
+}
+
+Asynapse.REST.VERSION = "0.10"
+
+Asynapse.REST.Model = function(model) {
+ this._model = model
+ return this;
+}
+
+Asynapse.REST.Model.prototype = {
+ /* Corresponds Jifty's REST Pluing API */
+ show_item_field: function(column, key, field) {
+ var url = "/=/model/*/*/*/*.js"
+ .replace("*", this._model)
+ .replace("*", column)
+ .replace("*", key)
+ .replace("*", field)
+
+ return this.eval_ajax_get(url);
+ },
+
+ show_item: function(column, key) {
+ var url = "/=/model/*/*/*.js"
+ .replace("*", this._model)
+ .replace("*", column)
+ .replace("*", key)
+
+ return this.eval_ajax_get(url);
+ },
+
+ list_model_items: function(column) {
+ var url = "/=/model/*/*.js"
+ .replace("*", this._model)
+ .replace("*", column)
+
+ return this.eval_ajax_get(url);
+ },
+
+ list_model_columns: function() {
+ var url = "/=/model/*.js"
+ .replace("*", this._model)
+
+ return this.eval_ajax_get(url);
+ },
+
+ list_models: function() {
+ var url = "/=/model.js"
+
+ return this.eval_ajax_get(url);
+ },
+
+ create_item: function(item) {
+ var url ="/=/model/*.js"
+ .replace("*", this._model)
+
+ var req = new Ajax.Request(url, {
+ method: 'post',
+ asynchronous: false,
+ postBody: $H(item).toQueryString()
+ });
+ if ( req.responseIsSuccess() ) {
+ eval(req.transport.responseText);
+ return $H($_)
+ } else {
+ return null;
+ }
+ },
+
+ replace_item: function(item) {
+ var url = "/=/action/update" + this._model + ".js"
+ new Ajax.Request(url, {
+ method: 'post',
+ contentType: 'application/x-www-form-urlencoded',
+ postBody: $H(item).toQueryString()
+ });
+ },
+
+ delete_item: function(column, key) {
+ var url = "/=/model/*/*/*"
+ .replace("*", this._model)
+ .replace("*", column)
+ .replace("*", key)
+
+ new Ajax.Request(url, {
+ method: 'DELETE',
+ contentType: 'application/x-www-form-urlencoded'
+ });
+ return null;
+ },
+
+ /* Internal Helpers */
+ eval_ajax_get: function(url) {
+ eval(this.ajax_get(url));
+ return $_ ? Object.extend({},$_) : null;
+ },
+ ajax_get: function(url) {
+ var req = new Ajax.Request(url, {
+ method: 'GET',
+ asynchronous: false
+ })
+ if ( req.responseIsSuccess() ) {
+ return req.transport.responseText;
+ }
+ else {
+ return "var $_ = null";
+ }
+ }
+}
+
+Asynapse.REST.Model.ActiveRecord = function(model) {
+ Object.extend(this, new Asynapse.REST.Model(model));
+ this._attributes = {}
+ return this;
+}
+
+Asynapse.REST.Model.ActiveRecord.prototype = {
+ new: function() {
+ return this;
+ },
+
+ find: function(param) {
+ if ( typeof param == 'number' ) {
+ return this.show_item("id", param)
+ }
+ },
+
+ find_by_id: function(id) {
+ return this.show_item("id", id)
+ },
+
+ create: function(attributes) {
+ var r = this.create_item(attributes);
+
+ if (r.success) {
+ return this.show_item("id", Number(r.content.id) )
+ }
+ return null;
+ },
+
+ delete: function(id) {
+ this.delete_item("id", id)
+ return null
+ },
+
+ update: function(id, attributes) {
+ var obj = this.find(id)
+ obj = Object.extend(obj, attributes)
+ return this.replace_item( obj )
+ },
+
+ write_attribute: function(attr, value) {
+ }
+}
+
+/* Great Aliases */
+Asynapse.Model = Asynapse.REST.Model
+Asynapse.ActiveRecord = Asynapse.REST.Model.ActiveRecord
+AsynapseRecord = Asynapse.REST.Model.ActiveRecord
+
+/**
+=head1 NAME
+
+Asynapse.REST - Asynapse REST Client
+
+=head1 VERSION
+
+This document describes Asynapse.REST version 0.10
+
+=head1 SYNOPSIS
+
+ # Define Your own AsynapseRecord Class.
+ Person = new AsynapseRecord('person')
+
+ # Use it
+ var p = Person.find(1)
+
+=head1 DESCRIPTION
+
+Asynapse.REST is the namespace for being a general REST client in
+Asynapse framework. Under this namespace, so far we arrange
+C<Asynapse.REST.Model> for Asynapse Model Classes. It means to
+provide an abstration layer for data existing at given REST server(s).
+
+With many flavours of data abstration layer in the world, we choose
+to emulate ActiveRecord as our first target, which has a plain
+simple object semantics, and very compatible to javascript.
+The implementation of it called C<AsynapseRecord>.
+
+To use it, you must first create your own record classes, like this:
+
+ Person = new AsynapseRecord('person')
+
+After this, Person becomes your Person model class, and then you
+can do:
+
+ var p = Person.find(1)
+
+To find a person by its id. Besides C<find>, C<create>, C<update>,
+and C<delete> are also implemented.
+
+Here's more detail about how to use these interfaces. They are all
+"class methods".
+
+=over
+
+=item find( id )
+
+Retrieve a record from this model with given id.
+
+=item create( attr )
+
+Create a new with attributes specified in attr hash.
+
+=item update( id, attr )
+
+Update the record with primary key id with new sets of
+attributes specified in attr hash.
+
+=item delete( id )
+
+Remove the record with given id.
+
+=back
+
+=head1 CONFIGURATION AND ENVIRONMENT
+
+AsynapseRecord requires no configuration files or environment
+variables. However, you need a Jifty instance with REST plugin
+(which is given by default now.)
+
+Since JavaScript cannot do XSS, it assumed the your Jifty instance's
+URL resides at C</>, and entry points of REST servces starts from
+C</=/>. It should be made possible to change this assumption in the
+future to match more presets in different frameworks.
+
+=head1 BUGS AND LIMITATIONS
+
+The asynapse project is hosted at L<http://code.google.com/p/asynapse/>.
+You may contact the authors or submit issues using the web interface.
+
+=head1 AUTHOR
+
+Kang-min Liu C<< <gugod at gugod.org> >>
+
+=head1 LICENCE AND COPYRIGHT
+
+Copyright (c) 2007, Kang-min Liu C<< <gugod at gugod.org> >>. All rights reserved.
+
+This module is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself. See L<perlartistic>.
+
+
+=head1 DISCLAIMER OF WARRANTY
+
+BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
+EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
+YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
+NECESSARY SERVICING, REPAIR, OR CORRECTION.
+
+IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
+LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
+OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
+THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+=cut
+
+*/
+
Added: jifty/branches/trimclient/examples/Yada/share/web/static/js/trimpath-template.js
==============================================================================
--- (empty file)
+++ jifty/branches/trimclient/examples/Yada/share/web/static/js/trimpath-template.js Mon Jun 11 17:57:19 2007
@@ -0,0 +1,397 @@
+/**
+ * TrimPath Template. Release 1.0.38.
+ * Copyright (C) 2004, 2005 Metaha.
+ *
+ * TrimPath Template is licensed under the GNU General Public License
+ * and the Apache License, Version 2.0, as follows:
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed 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.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var TrimPath;
+
+// TODO: Debugging mode vs stop-on-error mode - runtime flag.
+// TODO: Handle || (or) characters and backslashes.
+// TODO: Add more modifiers.
+
+(function() { // Using a closure to keep global namespace clean.
+ if (TrimPath == null)
+ TrimPath = new Object();
+ if (TrimPath.evalEx == null)
+ TrimPath.evalEx = function(src) { return eval(src); };
+
+ var UNDEFINED;
+ if (Array.prototype.pop == null) // IE 5.x fix from Igor Poteryaev.
+ Array.prototype.pop = function() {
+ if (this.length === 0) {return UNDEFINED;}
+ return this[--this.length];
+ };
+ if (Array.prototype.push == null) // IE 5.x fix from Igor Poteryaev.
+ Array.prototype.push = function() {
+ for (var i = 0; i < arguments.length; ++i) {this[this.length] = arguments[i];}
+ return this.length;
+ };
+
+ TrimPath.parseTemplate = function(tmplContent, optTmplName, optEtc) {
+ if (optEtc == null)
+ optEtc = TrimPath.parseTemplate_etc;
+ var funcSrc = parse(tmplContent, optTmplName, optEtc);
+ var func = TrimPath.evalEx(funcSrc, optTmplName, 1);
+ if (func != null)
+ return new optEtc.Template(optTmplName, tmplContent, funcSrc, func, optEtc);
+ return null;
+ }
+
+ try {
+ String.prototype.process = function(context, optFlags) {
+ var template = TrimPath.parseTemplate(this, null);
+ if (template != null)
+ return template.process(context, optFlags);
+ return this;
+ }
+ } catch (e) { // Swallow exception, such as when String.prototype is sealed.
+ }
+
+ TrimPath.parseTemplate_etc = {}; // Exposed for extensibility.
+ TrimPath.parseTemplate_etc.statementTag = "forelse|for|if|elseif|else|var|macro";
+ TrimPath.parseTemplate_etc.statementDef = { // Lookup table for statement tags.
+ "if" : { delta: 1, prefix: "if (", suffix: ") {", paramMin: 1 },
+ "else" : { delta: 0, prefix: "} else {" },
+ "elseif" : { delta: 0, prefix: "} else if (", suffix: ") {", paramDefault: "true" },
+ "/if" : { delta: -1, prefix: "}" },
+ "for" : { delta: 1, paramMin: 3,
+ prefixFunc : function(stmtParts, state, tmplName, etc) {
+ if (stmtParts[2] != "in")
+ throw new etc.ParseError(tmplName, state.line, "bad for loop statement: " + stmtParts.join(' '));
+ var iterVar = stmtParts[1];
+ var listVar = "__LIST__" + iterVar;
+ return [ "var ", listVar, " = ", stmtParts[3], ";",
+ // Fix from Ross Shaull for hash looping, make sure that we have an array of loop lengths to treat like a stack.
+ "var __LENGTH_STACK__;",
+ "if (typeof(__LENGTH_STACK__) == 'undefined' || !__LENGTH_STACK__.length) __LENGTH_STACK__ = new Array();",
+ "__LENGTH_STACK__[__LENGTH_STACK__.length] = 0;", // Push a new for-loop onto the stack of loop lengths.
+ "if ((", listVar, ") != null) { ",
+ "var ", iterVar, "_ct = 0;", // iterVar_ct variable, added by B. Bittman
+ "for (var ", iterVar, "_index in ", listVar, ") { ",
+ iterVar, "_ct++;",
+ "if (typeof(", listVar, "[", iterVar, "_index]) == 'function') {continue;}", // IE 5.x fix from Igor Poteryaev.
+ "__LENGTH_STACK__[__LENGTH_STACK__.length - 1]++;",
+ "var ", iterVar, " = ", listVar, "[", iterVar, "_index];" ].join("");
+ } },
+ "forelse" : { delta: 0, prefix: "} } if (__LENGTH_STACK__[__LENGTH_STACK__.length - 1] == 0) { if (", suffix: ") {", paramDefault: "true" },
+ "/for" : { delta: -1, prefix: "} }; delete __LENGTH_STACK__[__LENGTH_STACK__.length - 1];" }, // Remove the just-finished for-loop from the stack of loop lengths.
+ "var" : { delta: 0, prefix: "var ", suffix: ";" },
+ "macro" : { delta: 1,
+ prefixFunc : function(stmtParts, state, tmplName, etc) {
+ var macroName = stmtParts[1].split('(')[0];
+ return [ "var ", macroName, " = function",
+ stmtParts.slice(1).join(' ').substring(macroName.length),
+ "{ var _OUT_arr = []; var _OUT = { write: function(m) { if (m) _OUT_arr.push(m); } }; " ].join('');
+ } },
+ "/macro" : { delta: -1, prefix: " return _OUT_arr.join(''); };" }
+ }
+ TrimPath.parseTemplate_etc.modifierDef = {
+ "eat" : function(v) { return ""; },
+ "escape" : function(s) { return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); },
+ "capitalize" : function(s) { return String(s).toUpperCase(); },
+ "default" : function(s, d) { return s != null ? s : d; }
+ }
+ TrimPath.parseTemplate_etc.modifierDef.h = TrimPath.parseTemplate_etc.modifierDef.escape;
+
+ TrimPath.parseTemplate_etc.Template = function(tmplName, tmplContent, funcSrc, func, etc) {
+ this.process = function(context, flags) {
+ if (context == null)
+ context = {};
+ if (context._MODIFIERS == null)
+ context._MODIFIERS = {};
+ if (context.defined == null)
+ context.defined = function(str) { return (context[str] != undefined); };
+ for (var k in etc.modifierDef) {
+ if (context._MODIFIERS[k] == null)
+ context._MODIFIERS[k] = etc.modifierDef[k];
+ }
+ if (flags == null)
+ flags = {};
+ var resultArr = [];
+ var resultOut = { write: function(m) { resultArr.push(m); } };
+ try {
+ func(resultOut, context, flags);
+ } catch (e) {
+ if (flags.throwExceptions == true)
+ throw e;
+ var result = new String(resultArr.join("") + "[ERROR: " + e.toString() + (e.message ? '; ' + e.message : '') + "]");
+ result["exception"] = e;
+ return result;
+ }
+ return resultArr.join("");
+ }
+ this.name = tmplName;
+ this.source = tmplContent;
+ this.sourceFunc = funcSrc;
+ this.toString = function() { return "TrimPath.Template [" + tmplName + "]"; }
+ }
+ TrimPath.parseTemplate_etc.ParseError = function(name, line, message) {
+ this.name = name;
+ this.line = line;
+ this.message = message;
+ }
+ TrimPath.parseTemplate_etc.ParseError.prototype.toString = function() {
+ return ("TrimPath template ParseError in " + this.name + ": line " + this.line + ", " + this.message);
+ }
+
+ var parse = function(body, tmplName, etc) {
+ body = cleanWhiteSpace(body);
+ var funcText = [ "var TrimPath_Template_TEMP = function(_OUT, _CONTEXT, _FLAGS) { with (_CONTEXT) {" ];
+ var state = { stack: [], line: 1 }; // TODO: Fix line number counting.
+ var endStmtPrev = -1;
+ while (endStmtPrev + 1 < body.length) {
+ var begStmt = endStmtPrev;
+ // Scan until we find some statement markup.
+ begStmt = body.indexOf("{", begStmt + 1);
+ while (begStmt >= 0) {
+ var endStmt = body.indexOf('}', begStmt + 1);
+ var stmt = body.substring(begStmt, endStmt);
+ var blockrx = stmt.match(/^\{(cdata|minify|eval)/); // From B. Bittman, minify/eval/cdata implementation.
+ if (blockrx) {
+ var blockType = blockrx[1];
+ var blockMarkerBeg = begStmt + blockType.length + 1;
+ var blockMarkerEnd = body.indexOf('}', blockMarkerBeg);
+ if (blockMarkerEnd >= 0) {
+ var blockMarker;
+ if( blockMarkerEnd - blockMarkerBeg <= 0 ) {
+ blockMarker = "{/" + blockType + "}";
+ } else {
+ blockMarker = body.substring(blockMarkerBeg + 1, blockMarkerEnd);
+ }
+
+ var blockEnd = body.indexOf(blockMarker, blockMarkerEnd + 1);
+ if (blockEnd >= 0) {
+ emitSectionText(body.substring(endStmtPrev + 1, begStmt), funcText);
+
+ var blockText = body.substring(blockMarkerEnd + 1, blockEnd);
+ if (blockType == 'cdata') {
+ emitText(blockText, funcText);
+ } else if (blockType == 'minify') {
+ emitText(scrubWhiteSpace(blockText), funcText);
+ } else if (blockType == 'eval') {
+ if (blockText != null && blockText.length > 0) // From B. Bittman, eval should not execute until process().
+ funcText.push('_OUT.write( (function() { ' + blockText + ' })() );');
+ }
+ begStmt = endStmtPrev = blockEnd + blockMarker.length - 1;
+ }
+ }
+ } else if (body.charAt(begStmt - 1) != '$' && // Not an expression or backslashed,
+ body.charAt(begStmt - 1) != '\\') { // so check if it is a statement tag.
+ var offset = (body.charAt(begStmt + 1) == '/' ? 2 : 1); // Close tags offset of 2 skips '/'.
+ // 10 is larger than maximum statement tag length.
+ if (body.substring(begStmt + offset, begStmt + 10 + offset).search(TrimPath.parseTemplate_etc.statementTag) == 0)
+ break; // Found a match.
+ }
+ begStmt = body.indexOf("{", begStmt + 1);
+ }
+ if (begStmt < 0) // In "a{for}c", begStmt will be 1.
+ break;
+ var endStmt = body.indexOf("}", begStmt + 1); // In "a{for}c", endStmt will be 5.
+ if (endStmt < 0)
+ break;
+ emitSectionText(body.substring(endStmtPrev + 1, begStmt), funcText);
+ emitStatement(body.substring(begStmt, endStmt + 1), state, funcText, tmplName, etc);
+ endStmtPrev = endStmt;
+ }
+ emitSectionText(body.substring(endStmtPrev + 1), funcText);
+ if (state.stack.length != 0)
+ throw new etc.ParseError(tmplName, state.line, "unclosed, unmatched statement(s): " + state.stack.join(","));
+ funcText.push("}}; TrimPath_Template_TEMP");
+ return funcText.join("");
+ }
+
+ var emitStatement = function(stmtStr, state, funcText, tmplName, etc) {
+ var parts = stmtStr.slice(1, -1).split(' ');
+ var stmt = etc.statementDef[parts[0]]; // Here, parts[0] == for/if/else/...
+ if (stmt == null) { // Not a real statement.
+ emitSectionText(stmtStr, funcText);
+ return;
+ }
+ if (stmt.delta < 0) {
+ if (state.stack.length <= 0)
+ throw new etc.ParseError(tmplName, state.line, "close tag does not match any previous statement: " + stmtStr);
+ state.stack.pop();
+ }
+ if (stmt.delta > 0)
+ state.stack.push(stmtStr);
+
+ if (stmt.paramMin != null &&
+ stmt.paramMin >= parts.length)
+ throw new etc.ParseError(tmplName, state.line, "statement needs more parameters: " + stmtStr);
+ if (stmt.prefixFunc != null)
+ funcText.push(stmt.prefixFunc(parts, state, tmplName, etc));
+ else
+ funcText.push(stmt.prefix);
+ if (stmt.suffix != null) {
+ if (parts.length <= 1) {
+ if (stmt.paramDefault != null)
+ funcText.push(stmt.paramDefault);
+ } else {
+ for (var i = 1; i < parts.length; i++) {
+ if (i > 1)
+ funcText.push(' ');
+ funcText.push(parts[i]);
+ }
+ }
+ funcText.push(stmt.suffix);
+ }
+ }
+
+ var emitSectionText = function(text, funcText) {
+ if (text.length <= 0)
+ return;
+ var nlPrefix = 0; // Index to first non-newline in prefix.
+ var nlSuffix = text.length - 1; // Index to first non-space/tab in suffix.
+ while (nlPrefix < text.length && (text.charAt(nlPrefix) == '\n'))
+ nlPrefix++;
+ while (nlSuffix >= 0 && (text.charAt(nlSuffix) == ' ' || text.charAt(nlSuffix) == '\t'))
+ nlSuffix--;
+ if (nlSuffix < nlPrefix)
+ nlSuffix = nlPrefix;
+ if (nlPrefix > 0) {
+ funcText.push('if (_FLAGS.keepWhitespace == true) _OUT.write("');
+ var s = text.substring(0, nlPrefix).replace('\n', '\\n'); // A macro IE fix from BJessen.
+ if (s.charAt(s.length - 1) == '\n')
+ s = s.substring(0, s.length - 1);
+ funcText.push(s);
+ funcText.push('");');
+ }
+ var lines = text.substring(nlPrefix, nlSuffix + 1).split('\n');
+ for (var i = 0; i < lines.length; i++) {
+ emitSectionTextLine(lines[i], funcText);
+ if (i < lines.length - 1)
+ funcText.push('_OUT.write("\\n");\n');
+ }
+ if (nlSuffix + 1 < text.length) {
+ funcText.push('if (_FLAGS.keepWhitespace == true) _OUT.write("');
+ var s = text.substring(nlSuffix + 1).replace('\n', '\\n');
+ if (s.charAt(s.length - 1) == '\n')
+ s = s.substring(0, s.length - 1);
+ funcText.push(s);
+ funcText.push('");');
+ }
+ }
+
+ var emitSectionTextLine = function(line, funcText) {
+ var endMarkPrev = '}';
+ var endExprPrev = -1;
+ while (endExprPrev + endMarkPrev.length < line.length) {
+ var begMark = "${", endMark = "}";
+ var begExpr = line.indexOf(begMark, endExprPrev + endMarkPrev.length); // In "a${b}c", begExpr == 1
+ if (begExpr < 0)
+ break;
+ if (line.charAt(begExpr + 2) == '%') {
+ begMark = "${%";
+ endMark = "%}";
+ }
+ var endExpr = line.indexOf(endMark, begExpr + begMark.length); // In "a${b}c", endExpr == 4;
+ if (endExpr < 0)
+ break;
+ emitText(line.substring(endExprPrev + endMarkPrev.length, begExpr), funcText);
+ // Example: exprs == 'firstName|default:"John Doe"|capitalize'.split('|')
+ var exprArr = line.substring(begExpr + begMark.length, endExpr).replace(/\|\|/g, "#@@#").split('|');
+ for (var k in exprArr) {
+ if (exprArr[k].replace) // IE 5.x fix from Igor Poteryaev.
+ exprArr[k] = exprArr[k].replace(/#@@#/g, '||');
+ }
+ funcText.push('_OUT.write(');
+ emitExpression(exprArr, exprArr.length - 1, funcText);
+ funcText.push(');');
+ endExprPrev = endExpr;
+ endMarkPrev = endMark;
+ }
+ emitText(line.substring(endExprPrev + endMarkPrev.length), funcText);
+ }
+
+ var emitText = function(text, funcText) {
+ if (text == null ||
+ text.length <= 0)
+ return;
+ text = text.replace(/\\/g, '\\\\');
+ text = text.replace(/\n/g, '\\n');
+ text = text.replace(/"/g, '\\"');
+ funcText.push('_OUT.write("');
+ funcText.push(text);
+ funcText.push('");');
+ }
+
+ var emitExpression = function(exprArr, index, funcText) {
+ // Ex: foo|a:x|b:y1,y2|c:z1,z2 is emitted as c(b(a(foo,x),y1,y2),z1,z2)
+ var expr = exprArr[index]; // Ex: exprArr == [firstName,capitalize,default:"John Doe"]
+ if (index <= 0) { // Ex: expr == 'default:"John Doe"'
+ funcText.push(expr);
+ return;
+ }
+ var parts = expr.split(':');
+ funcText.push('_MODIFIERS["');
+ funcText.push(parts[0]); // The parts[0] is a modifier function name, like capitalize.
+ funcText.push('"](');
+ emitExpression(exprArr, index - 1, funcText);
+ if (parts.length > 1) {
+ funcText.push(',');
+ funcText.push(parts[1]);
+ }
+ funcText.push(')');
+ }
+
+ var cleanWhiteSpace = function(result) {
+ result = result.replace(/\t/g, " ");
+ result = result.replace(/\r\n/g, "\n");
+ result = result.replace(/\r/g, "\n");
+ result = result.replace(/^(\s*\S*(\s+\S+)*)\s*$/, '$1'); // Right trim by Igor Poteryaev.
+ return result;
+ }
+
+ var scrubWhiteSpace = function(result) {
+ result = result.replace(/^\s+/g, "");
+ result = result.replace(/\s+$/g, "");
+ result = result.replace(/\s+/g, " ");
+ result = result.replace(/^(\s*\S*(\s+\S+)*)\s*$/, '$1'); // Right trim by Igor Poteryaev.
+ return result;
+ }
+
+ // The DOM helper functions depend on DOM/DHTML, so they only work in a browser.
+ // However, these are not considered core to the engine.
+ //
+ TrimPath.parseDOMTemplate = function(elementId, optDocument, optEtc) {
+ if (optDocument == null)
+ optDocument = document;
+ var element = optDocument.getElementById(elementId);
+ var content = element.value; // Like textarea.value.
+ if (content == null)
+ content = element.innerHTML; // Like textarea.innerHTML.
+ content = content.replace(/</g, "<").replace(/>/g, ">");
+ return TrimPath.parseTemplate(content, elementId, optEtc);
+ }
+
+ TrimPath.processDOMTemplate = function(elementId, context, optFlags, optDocument, optEtc) {
+ return TrimPath.parseDOMTemplate(elementId, optDocument, optEtc).process(context, optFlags);
+ }
+}) ();
Modified: jifty/branches/trimclient/lib/Jifty/View/Declare/CRUD.pm
==============================================================================
--- jifty/branches/trimclient/lib/Jifty/View/Declare/CRUD.pm (original)
+++ jifty/branches/trimclient/lib/Jifty/View/Declare/CRUD.pm Mon Jun 11 17:57:19 2007
@@ -6,6 +6,14 @@
use base 'Exporter';
our @EXPORT = qw(object_type fragment_for get_record current_collection);
+# XXX: should register 'template type' handler, so the
+# client_cache_content & the TD sub here agrees with the arguments.
+use Attribute::Handlers;
+my %VIEW;
+sub CRUDView :ATTR(CODE,BEGIN) {
+ $VIEW{$_[2]}++;
+}
+
sub mount_view {
my ($class, $model, $vclass, $path) = @_;
my $caller = caller(0);
@@ -21,6 +29,19 @@
*{$vclass."::object_type"} = sub { $model };
}
+sub _dispatch_template {
+ my $class = shift;
+ my $code = shift;
+ if ($VIEW{$code} && !UNIVERSAL::isa($_[0], 'Evil')) {
+ my ( $object_type, $id ) = ( $class->object_type, get('id') );
+ @_ = ($class, $class->get_record($id), @_);
+ }
+ else {
+ unshift @_, $class;
+ }
+ goto $code;
+}
+
sub object_type {
my $self = shift;
return $self->package_variable('object_type') || get('object_type');
@@ -84,26 +105,24 @@
return grep { !( m/_confirm/ || lc $action->arguments->{$_}{render_as} eq 'password' ) } $action->argument_names;
}
-
-template 'view' => sub {
- my $self = shift;
- my ( $object_type, $id ) = ( $self->object_type, get('id') );
+template 'view' => sub :CRUDView {
+ my ($self, $record) = @_;
my $update = new_action(
- class => 'Update' . $object_type,
+ class => 'Update' . $self->object_type,
moniker => "update-" . Jifty->web->serial,
- record => $self->get_record($id)
+ record => $record,
);
div {
{ class is 'crud read item inline' };
- my @fields =$self->display_columns($update);
+ my @fields = $self->display_columns($update);
render_action( $update, \@fields, { render_mode => 'read' } );
hyperlink(
label => "Edit",
class => "editlink",
onclick => {
replace_with => $self->fragment_for('update'),
- args => { object_type => $object_type, id => $id }
+ args => { id => $record->id }
},
);
Modified: jifty/branches/trimclient/lib/Jifty/Web/PageRegion.pm
==============================================================================
--- jifty/branches/trimclient/lib/Jifty/Web/PageRegion.pm (original)
+++ jifty/branches/trimclient/lib/Jifty/Web/PageRegion.pm Mon Jun 11 17:57:19 2007
@@ -384,4 +384,36 @@
return "#region-" . $self->qualified_name . ' ' . join(' ', @_);
}
+sub client_cacheable {
+ my $self = shift;
+ return 'static' if $self->path eq '__jifty/empty';
+ return 'crudview' if $self->path eq '//todo/view';
+}
+
+sub client_cache_content {
+ my $self = shift;
+ my $code = Template::Declare->resolve_template($self->path);
+
+ local @Evil::ISA = ('Yada::Model::Todo');
+ local $HTML::Mason::Commands::m = undef;
+ local Jifty->handler->apache->{http_header_sent} = 1;
+
+ # dup from JV::Declare::Handler
+ no warnings qw/redefine/;
+ local *Jifty::Web::out = sub {
+ shift; # Turn the method into a function
+ goto &Template::Declare::Tags::outs_raw;
+ };
+
+ return Template::Declare::Tags::show_page( $self->path, (bless {}, 'Evil') );
+}
+
+package Evil;
+
+sub id { return '${id}' }
+
+sub _value {
+ return '${'.$_[1].'}';
+}
+
1;
Modified: jifty/branches/trimclient/share/web/static/js/jifty.js
==============================================================================
--- jifty/branches/trimclient/share/web/static/js/jifty.js (original)
+++ jifty/branches/trimclient/share/web/static/js/jifty.js Mon Jun 11 17:57:19 2007
@@ -652,6 +652,26 @@
return f;
}
+
+var CACHE = {};
+
+var extract_cacheable = function(fragment, f) {
+ for (var fragment_bit = fragment.firstChild;
+ fragment_bit != null;
+ fragment_bit = fragment_bit.nextSibling) {
+ if (fragment_bit.nodeName == 'cacheable') {
+ var c_type = fragment_bit.getAttribute("type");
+ var textContent = '';
+ if (fragment_bit.textContent) {
+ textContent = fragment_bit.textContent;
+ } else if (fragment_bit.firstChild) {
+ textContent = fragment_bit.firstChild.nodeValue;
+ }
+ CACHE[f['path']] = { 'type': c_type, 'content': textContent };
+ }
+ }
+}
+
// applying updates from a fragment
// - fragment: the fragment from the server
// - f: fragment spec
@@ -659,6 +679,7 @@
// We found the right fragment
var dom_fragment = fragments[f['region']];
var new_dom_args = $H();
+
var element = f['element'];
for (var fragment_bit = fragment.firstChild;
fragment_bit != null;
@@ -732,7 +753,6 @@
window.event.returnValue = false;
}
- show_wait_message();
var named_args = arguments[0];
var trigger = arguments[1];
@@ -759,6 +779,7 @@
if (form && form['J:CALL'])
optional_fragments = [ prepare_element_for_update({'mode':'Replace','args':{},'region':'__page','path': null}) ];
// Build actions structure
+ var has_request = 0;
request['actions'] = $H();
for (var moniker in named_args['actions']) {
var disable = named_args['actions'][moniker];
@@ -775,15 +796,47 @@
a.disable_input_fields();
}
request['actions'][moniker] = a.data_structure();
+ ++has_request;
}
+
}
request['fragments'] = $H();
+ var update_from_cache = new Array;
+
// Build fragments structure
for (var i = 0; i < named_args['fragments'].length; i++) {
var f = named_args['fragments'][i];
f = prepare_element_for_update(f);
if (!f) continue;
+
+ var cached = CACHE[f['path']];
+ if (cached && cached['type'] == 'static') {
+ var my_fragment = document.createElement('fragment');
+ var content_node = document.createElement('content');
+ content_node.textContent = cached['content'];
+ my_fragment.appendChild(content_node);
+ my_fragment.setAttribute('id', f['region']);
+
+ update_from_cache.push(function(){ apply_fragment_updates(my_fragment, f);
+ } );
+ continue;
+ }
+ else if (cached && cached['type'] == 'crudview') {
+ try { // XXX: get model class etc as metadata in cache
+ // XXX: kill dup code
+ var Todo = new AsynapseRecord('todo');
+ var record = Todo.find(f['args']['id']);
+ var my_fragment = document.createElement('fragment');
+ var content_node = document.createElement('content');
+ content_node.textContent = cached['content'].process(record);
+ my_fragment.appendChild(content_node);
+ my_fragment.setAttribute('id', f['region']);
+ update_from_cache.push(function(){ apply_fragment_updates(my_fragment, f); } );
+ } catch (e) { alert(e) };
+ continue;
+ }
+
// Update with all new values
var name = f['region'];
var fragment_request = fragments[name].data_structure(f['path'], f['args']);
@@ -794,8 +847,17 @@
// Push it onto the request stack
request['fragments'][name] = fragment_request;
+ ++has_request;
}
+ if (!has_request) {
+ for (var i = 0; i < update_from_cache.length; i++)
+ update_from_cache[i]();
+ return false;
+ }
+
+ show_wait_message();
+
// And when we get the result back..
var onSuccess = function(transport, object) {
// Grab the XML response
@@ -815,9 +877,13 @@
if (response_fragment.getAttribute("id") != f['region'])
continue;
- try {
- apply_fragment_updates(response_fragment, f);
- }catch (e) { alert(e) }
+ for (var i = 0; i < update_from_cache.length; i++)
+ update_from_cache[i]();
+
+ try {
+ apply_fragment_updates(response_fragment, f);
+ }catch (e) { alert(e) }
+ extract_cacheable(response_fragment, f);
}
for (var result = response.firstChild;
result != null;
Modified: jifty/branches/trimclient/share/web/templates/__jifty/webservices/xml
==============================================================================
--- jifty/branches/trimclient/share/web/templates/__jifty/webservices/xml (original)
+++ jifty/branches/trimclient/share/web/templates/__jifty/webservices/xml Mon Jun 11 17:57:19 2007
@@ -52,6 +52,9 @@
$writer->startTag( "fragment", id => Jifty->web->current_region->qualified_name );
my %args = %{ Jifty->web->current_region->arguments };
$writer->dataElement( "argument", $args{$_}, name => $_) for sort keys %args;
+ if (Jifty->web->current_region->client_cacheable) {
+ $writer->cdataElement( "cacheable", Jifty->web->current_region->client_cache_content, type => Jifty->web->current_region->client_cacheable );
+ }
$writer->cdataElement( "content", Jifty->web->current_region->as_string );
$writer->endTag();
More information about the Jifty-commit
mailing list