jsviews
Version:
Next-generation MVVM and MVP framework - built on top of JsRender templates. Bringing templates to life...
962 lines (701 loc) • 36.4 kB
text/typescript
;
(function() {
function log(code: string, message?: string, success?: boolean) {
message = message || '';
if (document && document.body) {
if (message) {
message = $.views.converters.html(message);
message = success === undefined ? "<br/>==== <b><em>" + message + "</em></b> ====<br/>" : message;
}
if (success === false) {
message += ": <b>Failure</b>";
}
if (code !== undefined) {
code = $.views.converters.html(code);
message = arguments.length>1 ? "<code>" + code + "...</code> " + message : "log: " + "<code>" + code + "</code> ";
}
$(document.body).append(message + "<br/>");
} else {
if (success === undefined) {
message = "==== " + message + " ====";
} else {
if (code) {
message = code + "... " + message;
}
if (!success) {
message += ": Failure";
}
}
console.log(message);
}
}
let assert = {
equal: function(a: string, b: string, code: string, message?: string) {
log(code, message || '', a === b);
},
ok: function(a: boolean, code: string, message?: string) {
log(code, message || '', a);
},
testGroup: function(message: string) {
log('', message);
}
};
function text() {
return $("#result").text();
}
$(document.body).append('<div style="display: none;" id="result"></div>');
$.views.settings.trigger(false);
/*<<<<<<<<<<<<<*/ assert.testGroup("$.templates, and basic link"); /*>>>>>>>>>>>>>*/
$(document.body).append('<script id="myTmpl" type="text/x-jsrender">{^{:name}} </script>');
let tmpl = $.templates("#myTmpl");
assert.ok(tmpl.markup === "{^{:name}} ", 'tmpl = $.templates("#myTmpl")');
assert.ok($.views.templates("#myTmpl").markup === "{^{:name}} ", 'tmpl = $.views.templates("#myTmpl")');
assert.ok($.views.templates === $.templates, '$.views.templates === $.templates');
let data = { name: "Jo" };
tmpl.link("#result", data);
assert.ok(text() === "Jo ", 'tmpl.link(...)', 'Template link');
$.templates("myTmpl", "#myTmpl");
data = { name: "Bob" };
$.templates.myTmpl.link("#result", data);
assert.ok(text() === "Bob ", '$.templates("myTmpl", "#myTmpl"); $.templates.myTmpl.link(...)', 'Named template as expando on $.templates');
let array = [{ name: "Jo" }, { name: "Amy" }, { name: "Bob" }];
tmpl.link("#result", array);
assert.ok(text() === "Jo Amy Bob ", 'tmpl.link("#result", array)', 'Link array');
let helpers = {
title: "Mr"
};
tmpl = $.templates("tmplFromString", "{^{:~title}} {^{:name}}. ");
assert.ok(tmpl.tmplName + tmpl.markup + tmpl.useViews === "tmplFromString{^{:~title}} {^{:name}}. true", 'tmpl.tmplName + tmpl.markup + tmpl.useViews', 'tmpl properties');
/*<<<<<<<<<<<<<*/ assert.testGroup("link() access helpers and set noIteration"); /*>>>>>>>>>>>>>*/
tmpl.link("#result", array, helpers);
assert.ok(text() === "Mr Jo. Mr Amy. Mr Bob. ", 'tmpl.link(..., array, helpers)', 'Access helpers');
$.views.helpers("title", "Sir");
tmpl.link("#result", array, helpers);
assert.ok(text() === "Mr Jo. Mr Amy. Mr Bob. ", 'tmpl.link(..., array, helpers)', 'Access helpers');
tmpl.link("#result", array);
assert.ok(text() === "Sir Jo. Sir Amy. Sir Bob. ", 'tmpl.link(..., array, helpers)', 'Access helpers');
tmpl = $.templates("{^{:length}} {^{for}}{^{:~title}} {^{:name}} {{/for}}");
tmpl.link("#result", array, helpers, true);
assert.ok(text() === "3 Mr Jo Mr Amy Mr Bob ", 'tmpl.link(..., array, helpers, true)', 'Link array, no iteration');
tmpl.link("#result", array, true);
assert.ok(text() === "3 Sir Jo Sir Amy Sir Bob ", 'tmpl.link(..., array, true)', 'Link array, no iteration');
$("#result").link(tmpl, array, helpers, true);
assert.ok(text() === "3 Mr Jo Mr Amy Mr Bob ", '$("#result").link(tmpl, array, helpers, true)', 'Link array, no iteration');
$("#result").link(tmpl, array, true);
assert.ok(text() === "3 Sir Jo Sir Amy Sir Bob ", '$("#result").link(tmpl, array, true)', 'Link array, no iteration');
$.observable(array).insert({name: "Jane"});
$.observable(helpers).setProperty({title: "Chief"});
$.observable(array[0]).setProperty({name: "Fiona"});
$("#result").link(tmpl, array, helpers, true);
assert.ok(text() === "4 Chief Fiona Chief Amy Chief Bob Chief Jane ", '$("#result").link(tmpl, array, helpers, true)', 'Observable changes');
$.views.helpers("title", null);
tmpl.link("#result", array, true);
assert.ok(text() === "4 Fiona Amy Bob Jane ", '$.views.helpers("title", null); ...tmpl.link(..., array, true)', 'Unregister named helper, then link array, no iteration');
/*<<<<<<<<<<<<<*/ assert.testGroup("Compile template with private resources"); /*>>>>>>>>>>>>>*/
tmpl = $.templates({
markup: "{^{:~title}}{^{:~title2}}{^{:~title3}} {^{upper:name}} {^{full/}} {{include tmpl='inner2'/}}{{include tmpl='inner'/}}",
converters: { // private converter
upper: function(val) {
return val.toUpperCase();
}
},
tags: { // private tag
full: "{{upper:~title}} {{:name}}"
},
helpers: { // private helper
title: "Mr"
},
templates: { // private template
inner: "Inner: {{:~title}} {{:name}} {{full/}} {{short/}}"
}
});
$.views.converters({lower: function(val) {return val.toLowerCase();}}, tmpl); // Additional private converter
$.templates("inner2", "Inner2", tmpl); // Additional private template
$.views.helpers({title2: "Sir",
title3: "Ms",
myob: {amount: 33},
myfn: function(a: number) {return a + 10;}
}, tmpl); // Additional private helpers
$.views.tags("short", "{{lower:name}} ", tmpl); // Additional private tag
tmpl.link("#result", array);
$.observable(array).remove(0);
assert.ok(text() === "MrSirMs AMY MR Amy Inner2Inner: Mr Amy MR Amy amy MrSirMs BOB MR Bob Inner2Inner: Mr Bob MR Bob bob MrSirMs JANE MR Jane Inner2Inner: Mr Jane MR Jane jane ",
'tmpl = $.templates({markup: ..., converters: tags ... etc', 'Compile template with resources');
assert.equal(
tmpl.converters.upper("jo") +
tmpl.converters.lower("JO") +
tmpl.tags.short.template.markup +
tmpl.helpers.title +
tmpl.helpers.title2 +
tmpl.helpers.title3 +
tmpl.helpers.myob.amount +
tmpl.helpers.myfn(5) +
tmpl.templates.inner.markup +
tmpl.templates.inner2.markup,
"JOjo{{lower:name}} MrSirMs3315Inner: {{:~title}} {{:name}} {{full/}} {{short/}}Inner2",
'tmpl.converters.upper("jo") ... +tmpl.templates.inner2.markup',
"Accessing tmpl resources");
/*<<<<<<<<<<<<<*/ assert.testGroup("template.useViews"); /*>>>>>>>>>>>>>*/
assert.ok(!$.templates("{{for/}}").useViews, '$.templates("{{for/}}").useViews' , "useViews defaults to false");
assert.ok($.templates({
markup: "{{for/}}",
useViews: true
}).useViews, '$.templates({ ... useViews: true, ...})', "useViews forced to true");
assert.ok($.templates("{{for}}{{:#parent}}{{/for}}").useViews, '$.templates("{{for}}{{:#parent}}{{/for}}").useViews', "useViews defaults to true");
/*<<<<<<<<<<<<<*/ assert.testGroup("$.views.tags()"); /*>>>>>>>>>>>>>*/
let tag = $.views.tags("add", function(val1, val2) { return val1 + "|" + val2; });
let test = tag._is;
tmpl = $.templates("{^{add first last foo=last/}} {^{privateadd first last /}}");
tag = $.views.tags("privateadd", function(val1, val2) { return val1 + "!!" + val2; }, tmpl);
test += tag._is;
assert.equal(test + tmpl({first: "A", last: "B"}), "tagtagA|B A!!B", '$.views.tags("add", function() { ... })', "create tag from function, public or private");
$.views
.tags({add: "{{: ~tagCtx.args[0] + '&' + ~tagCtx.args[1]}}"}) // Create public tag (replaces previous version)
.tags({privateadd: "{{: ~tagCtx.args[0] + '$$' + ~tagCtx.args[1]}}"}, tmpl); // Create private tag (replaces previous version)
assert.equal(tmpl({first: "A", last: "B"}), "A&B A$$B", '$.views.tags("add", "...")', "create tag from string, public or private");
$.views.tags("add", {
init: function(tagCtx, linkCtx, ctx) {
this.baseApply(arguments);
test = this.sortDataMap;
this.foo = tagCtx.props.foo;
test = this.render();
this.template = {markup: "bar"};
},
render: function(val1, val2) {
return val1 + "==" + val2 + ":" + this.foo + "?" + this.ctxPrm("xfoo");
},
template: {markup: "none"},
baseTag: "for",
contentCtx: true, // function() { return "aaa"; },
convert: function(val1, val2) {
return [val1.toLowerCase(), val2.toLowerCase()];
},
argDefault: true,
bindTo: [0, "foo"],
bindFrom: [0, "foo"],
flow: false,
ctx: { x: 'myctx' },
dataBoundOnly: true,
boundProps: ["a", "b"],
depends: function() { return "foo"; },
mapProps: ["a2", "b2"],
mapDepends: function() { return "foo"; },
setSize: true,
height: 23,
width: "3em",
className: "blue",
linkedElement: [".a", "b"],
linkedCtxParam: ["w", "xfoo"],
mainElement: "#d",
displayElement: "#e",
trigger: true,
attr: "html",
dataMap: null,
lateRender: false,
onAfterLink: function(tagCtx, linkCtx, ctx, ev, eventArgs) {},
onBind: function(tagCtx, linkCtx, ctx, ev, eventArgs) {},
onUnbind: function(tagCtx, linkCtx, ctx, ev, eventArgs) {},
onUpdate: false,
onDispose: function() {},
convertBack: "upper",
onBeforeUpdateVal: function(ev, eventArgs) {},
onBeforeChange: function(ev, eventArgs) {},
onAfterChange: function(ev, eventArgs) {},
onArrayChange: function(ev, eventArgs) {},
setValue: function(value, index, elseBlock) {},
domChange: function() {},
});
$.views.tags({privateadd: {
template: {markup: "none"},
}}, tmpl);
tmpl.link("#result", {first: "Aval", last: "Bval"});
test = $.views.tags.add.foo;
assert.equal(text(), "aval==Bval:Bval?bval none", '$.views.tags("add", {...})', "create tag from tagOptions hash, public or private");
/*<<<<<<<<<<<<<*/ assert.testGroup("$.views.converters()"); /*>>>>>>>>>>>>>*/
let converter = $.views.converters("add", function(val1, val2) { return val1 + "|" + val2 + ", "; });
test = converter("a", "b");
tmpl = $.templates("{{add: first last}} {{privateadd: first last}}");
converter = $.views.converters({privateadd: function(val1, val2) { return val1 + "!!" + val2 + ", "; }}, tmpl)
.converters.privateadd; // returns views, then access private converter resource 'privateadd'
test += converter("c", "d");
assert.equal(test + "--" + tmpl({first: "A", last: "B"}), "a|b, c!!d, --A|B, A!!B, ",
'$.views.converters("add", function() { ... })', "register converter, public or private");
converter = $.views.converters("add", null);
assert.ok(converter === null && $.views.converters.add === undefined, '$.views.converters("...", null)', "unregister converter");
/*<<<<<<<<<<<<<*/ assert.testGroup("$.views.helpers()"); /*>>>>>>>>>>>>>*/
let helper = $.views.helpers("add", function(val1: string, val2: string) { return val1 + "|" + val2 + ", "; });
test = helper("a", "b");
tmpl = $.templates("{{:~add(first, last)}} {{:~privateadd(first, last)}}");
helper = $.views.helpers({privateadd: function(val1: string, val2: string) { return val1 + "!!" + val2 + ", "; }}, tmpl)
.helpers.privateadd; // returns views, then access private helper resource 'privateadd'
test += helper("c", "d");
assert.equal(test + "--" + tmpl({first: "A", last: "B"}), "a|b, c!!d, --A|B, A!!B, ",
'$.views.helpers("add", function() { ... })', "register helper, public or private");
helper = $.views.helpers("add", null);
assert.ok(helper === null && $.views.helpers.add === undefined, '$.views.helpers("...", null)', "unregister helper");
/*<<<<<<<<<<<<<*/ assert.testGroup("$.views.viewModels()"); /*>>>>>>>>>>>>>*/
let Book = $.views.viewModels({
getters: ["title", "price"],
extend: {nameAndPrice: function(reverse?: boolean) {
return reverse ? this._price + " for " + this._title : this._title + ": " + this._price;
}}
});
let book1 = Book("Hope", "$1.50");
assert.ok(book1.title() + ": " + book1.price() === "Hope: $1.50"
&& book1.nameAndPrice() === "Hope: $1.50"
&& book1.nameAndPrice(true) === "$1.50 for Hope",
'VM=$.views.viewModels(vmOptions)', "Create VM, instantiate and access members");
tmpl = $.templates("Title: {{:title()}}, Price: {{:price()}}, PriceName: {{:nameAndPrice(true)}}");
tmpl.link("#result", book1);
assert.ok(text() === "Title: Hope, Price: $1.50, PriceName: $1.50 for Hope",
'tmpl.link("#result", book1)', "Link vm instance with template");
book1.title(book1.title() + "+");
book1.price(book1.price() + "+");
tmpl.link("#result", book1);
assert.ok(text() === "Title: Hope+, Price: $1.50+, PriceName: $1.50+ for Hope+",
'book1.title(newValue)', "Modify vm instance, with setters, then link template");
let MyVMs: JsViews.Hash<JsViews.ViewModel> = {};
Book = $.views.viewModels("Bk", {
getters: ["title", "price"],
extend: {nameAndPrice: function(reverse: boolean) {
return reverse ? this._price + " for " + this._title : this._title + ":" + this._price;
}}
});
assert.ok(Book===$.views.viewModels.Bk, '$.views.viewModels("Bk", vmOptions)', "Register named VM");
Book = $.views.viewModels("Bk", {
getters: ["title", "price"],
extend: {nameAndPrice: function(reverse: boolean) {
return reverse ? this._price + " for " + this._title : this._title + ":" + this._price;
}}
}, MyVMs);
assert.ok(Book===MyVMs.Bk, '$.views.viewModels("Bk", vmOptions, MyVMs)', "Register named VM on local MyVMs collection");
$.views.viewModels({
Bk: {
getters: ["title", "price"],
extend: {nameAndPrice: function(reverse: boolean) {
return reverse ? this._price + " for " + this._title : this._title + ":" + this._price;
}}
}
});
$.views.viewModels({
Bk: {
getters: ["title", "price"],
extend: {nameAndPrice: function(reverse: boolean) {
return reverse ? this._price + " for " + this._title : this._title + ":" + this._price;
}}
}
}, MyVMs);
test = $.views.viewModels.Bk("Hope", "$1.50").title();
assert.ok(test === "Hope", '$.views.viewModels({Bk: vmOptions})', "Register one or more named VMs");
test = MyVMs.Bk("Hope", "$1.50").title();
assert.ok(test === "Hope", '$.views.viewModels({Bk: vmOptions}, MyVMs)', "Register one or more named VMs on local myVms collection");
let bookData1 = {title: "Faith", price: "$10.50"}; // book (plain object)
book1 = Book.map(bookData1); // book (instance of Book View Model)
assert.ok(book1.title() === "Faith", 'Book.map(...)', "Instantiate vm instance from data, with map()");
book1.merge({ title: "Hope2", price: "$1.50" });
assert.ok(book1.title() === "Hope2", 'book.merge(...)', "Modify vm instance from data, with merge()");
test = book1.unmap();
assert.ok(test.title === "Hope2" && test.price === "$1.50", 'book.unmap()', "Round-trip data changes back to data, using unmap()");
let bookDataArray1 = [ // book array (plain objects)
{title: "Hope", price: "$1.50"},
{title: "Courage", price: "$2.50"}
];
let booksArray1 = Book.map(bookDataArray1); // book array (instances of Book View Model)
booksArray1.merge([
{title: "Hope2", price: "$1.50"},
{title: "Courage", price: "$222.50"}
]);
test = booksArray1.unmap();
assert.ok(test[1].title === "Courage" && test[1].price === "$222.50",
'bookArray = Book.map(dataArray) bookArray.merge(...) bookArray.unmap()', "Round-trip data array to array of book vm instances");
tmpl = $.templates("Name: {{:name()}}, Street: {{:address().street()}}, Phones:" +
"{{for phones()}} {{:number()}} ({{:person.name()}}) {{/for}}");
// The following code is from sample: https://www.jsviews.com/#viewmodelsapi@mergesampleadv plus use of parentRef
let myVmCollection: JsViews.Hash<JsViews.ViewModel> = {};
interface Person {
_name: string;
_comment: string;
_address: Address;
phones: () => Phone[];
}
interface Address {
_street: string;
}
interface Phone {
_number: string;
id: string;
}
$.views.viewModels({
Person: {
getters: [
{getter: "name", defaultVal: "No name"}, // Compiled name() get/set
{getter: "address", type: "Address", defaultVal: defaultAddress},
{getter: "phones", type: "Phone", defaultVal: [], parentRef: "person"}
],
extend: {
name: myNameGetSet, // Override name() get/set
addPhone: addPhone,
comment: comment // Additional get/set property, not initialized by data)
},
id: function(vm, plain) { // Callback function to determine 'identity'
return vm.personId === plain.personId;
}
},
Address: {
getters: ["street"]
},
Phone: {
getters: ["number"],
id: "phoneId" // Treat phoneId as 'primary key', for identity
}
}, myVmCollection); // Store View Models (typed hierarchy) on myVmCollection
// Override generated name() get/set
function myNameGetSet(this: Person, val: string) {
if (!arguments.length) { // This is standard compiled get/set code
return this._name; // If there is no argument, use as a getter
}
this._name = val; // If there is an argument, use as a setter
}
// Method for Person class
function addPhone(this: Person, phoneNo: string) { // Uses myVmCollection.Phone() to construct new instance
this.phones().push(myVmCollection.Phone(phoneNo, "person", this));
}
// get/set for comment (state on View Model instance, not initialized from data)
function comment(this: Person, val: string) {
if (!arguments.length) {
return this._comment; // If there is no argument, use as a getter
}
this._comment = val;
}
function defaultAddress(this: {name: string}) { // Function providing default address if undefined in data
return {street: 'No street for "' + this.name + '"'};
}
// First version of data - array of objects (e.g. from JSON request):
let peopleData = [
{
personId: "1",
address: {
street: "2nd Ave"
}
},
{
personId: "2",
name: "Pete",
phones: [
{number: "333 333 3333", phoneId: "2a"}
]
}
];
// Second version of data - JSON string (e.g. new JSON request):
let peopleData2 = '[{"personId":"2","name":"Peter","address":{"street":"11 1st Ave"},'
+ '"phones":[{"number":"111 111 9999","phoneId":"1a"},{"number":"333 333 9999","phoneId":"2a"}]}]';
// Instantiate View Model hierarchy using map()
let people = myVmCollection.Person.map(peopleData);
// Link template against people (array of Person instances)
tmpl.link("#result", people);
assert.equal(text(), 'Name: No name, Street: 2nd Ave, Phones:Name: Pete, Street: No street for "Pete", Phones: 333 333 3333 (Pete) ',
'Person.map(peopleData)', "map data to full VM hierarchy");
people.merge(peopleData2);
tmpl.link("#result", people);
assert.equal(text(), 'Name: Peter, Street: 11 1st Ave, Phones: 111 111 9999 (Peter) 333 333 9999 (Peter) ',
'people.merge(peopleData2)', "Merge data on full hierarchy");
people.merge(peopleData);
tmpl.link("#result", people);
assert.equal(text(), 'Name: No name, Street: 2nd Ave, Phones:Name: Pete, Street: No street for "Pete", Phones: 333 333 3333 (Pete) ',
'people.merge(peopleData)', "Merge back to previous data on full hierarchy");
people[0].name("newName");
tmpl.link("#result", people);
assert.equal(text(), 'Name: newName, Street: 2nd Ave, Phones:Name: Pete, Street: No street for "Pete", Phones: 333 333 3333 (Pete) ',
'people[0].name("newName")', "Change a property, deep in hierarchy");
people[0].addPhone("xxx xxx xxxx");
tmpl.link("#result", people);
assert.equal(text(), 'Name: newName, Street: 2nd Ave, Phones: xxx xxx xxxx (newName) Name: Pete, Street: No street for "Pete", Phones: 333 333 3333 (Pete) ',
'people[0].addPhone("xxx xxx xxxx")', "Insert instance, deep in hierarchy");
let updatedPeopleData = people.unmap();
assert.ok(updatedPeopleData[0].name === "newName" && updatedPeopleData[0].phones[0].number === "xxx xxx xxxx",
'updatedPeopleData = people.unmap()', "Round-trip back to data");
/*<<<<<<<<<<<<<*/ assert.testGroup("view"); /*>>>>>>>>>>>>>*/
test = '';
$.views.helpers("hlp", "Hlp");
$.views.tags("mytag", {
init: function(tagCtx, linkCtx, ctx) {
let view = tagCtx.view;
test = ""
+ (view === this.tagCtx.view)
+ view.ctxPrm("foo")
+ "(" + view.getIndex() + ")";
},
contentCtx: function(arg0) {
return this.ctx.root; // The returned value will be the data context inside {{mytag}}
},
show: function(this: JsViews.Tag, view: JsViews.View) {
test += "show";
},
do: function(this: JsViews.Tag, tagCtx: JsViews.TagCtx, ev: JsViews.EventObject, eventArgs: JsViews.EvtArgs) {
data3.a = "A2";
let view = tagCtx.contentView;
view.refresh();
view.ctxPrm("x", "X");
test += view.ctxPrm("x")
+ (view.parent.views._1 === view && view.parent === this.tagCtx.view)
+ view.content.markup.slice(0, 8)
+ view.type
+ view.parent.type
+ (view.get("mytag").get(true, "mytag") === view)
+ view.get("array").get(true, "item").index
+ view.root.type
+ view.getRsc("helpers", "hlp")
+ " contents: " + view.contents().length + view.contents(true).length + view.contents("em").length + view.contents(true, "em").length
+ " childTags: " + view.parent.childTags().length + view.parent.childTags(true).length + view.parent.childTags("on").length + view.parent.childTags(true, "on").length
+ " nodes: " + view.nodes().length;
},
template: "startTag {^{on ~tag.do ~tagCtx id='btn'/}} {{include tmpl=#content /}} endTag"
});
tmpl = $.templates("{^{for contentCtx=true start=0 end=1 ~foo ='FOO'}}{^{mytag a 'b' 33}} in tag {^{:~tag.show(#view)}} {^{:a}} <span><em>inspan</em></span> <input data-link='a' id='inp1'/> <input data-link='~hlp' id='inp2'/>{{/mytag}}{{/for}}");
let data3 = {a: "A"};
tmpl.link("#result", data3, {ctxHlp: "Ctxhlp"});
$("#btn").click();
assert.equal(test,
"trueFOO(0)showshowshowshowXtrue in tag mytagitemtrue0dataHlp contents: 13501 childTags: 1201 nodes: 13",
'view.get(), view.parent, view.data, view.contents(), view.childTags(), view.root etc etc', "View APIs tested");
/*<<<<<<<<<<<<<*/ assert.testGroup("tagCtx, linkCtx"); /*>>>>>>>>>>>>>*/
$.views.tags("mytag", {
linkedElement: ["input", "#inp"],
mainElement: "input",
displayElement: "#inp",
bindTo: [0, "bar"],
init: function(tagCtx, linkCtx, ctx) {
test = '' + linkCtx
+ tagCtx.ctxPrm("foo")
+ tagCtx.view.type
+ tagCtx.args[1]
+ tagCtx.props.bar
+ tagCtx.ctxPrm("foo")
+ (tagCtx.ctx === ctx)
+ tagCtx.bndArgs()
+ tagCtx.cvtArgs()
+ tagCtx.index
+ (this.tagCtxs[0] === tagCtx)
+ tagCtx.params.props.bar
+ (tagCtx.params.ctx && tagCtx.params.ctx.foo)
+ !!tagCtx.render(0, {foo: "FOO2"})
+ (tagCtx.tag === this)
+ tagCtx.tag.tagName
+ (tagCtx.tmpl === null)
+ (tagCtx.tmpl && tagCtx.tmpl.markup)
+ (tagCtx.content && tagCtx.content.markup);
},
onBind: function(tagCtx, linkCtx, ctx) {
test += " tagCtx " + tagCtx.linkedElems[1][0].outerHTML
+ tagCtx.mainElem[0].tagName
+ tagCtx.displayElem[0].tagName
+ tagCtx.contentView.type
+ tagCtx.nodes()[0].textContent
+ tagCtx.contents("#inp")[0].id
+ tagCtx.childTags().length
+ $.isFunction(tagCtx.setValues);
},
onAfterLink: function(tagCtx, linkCtx, ctx) {
test += " linkCtx " +linkCtx.data.a
+ linkCtx.elem.tagName
+ linkCtx.elem.tagName
+ (linkCtx.view === tagCtx.view)
+ linkCtx.attr
+ linkCtx.tag.tagName
+ linkCtx.ctx.root.a
+ linkCtx.type;
}
});
tmpl = $.templates("{^{mytag a 'mode' bar=b ~foo='FOO'}}inner{^{:~foo}}<input/><input id='inp'/>{{/mytag}}");
tmpl.link("#result", {a: "A", b: "B"});
assert.equal(test, "falseFOOdatamodeBFOOtrueA,modeA,mode0trueb'FOO'truetruemytagfalseinner{^{:~foo}}<input/><input id='inp'/>inner{^{:~foo}}<input/><input id='inp'/>"
+ ' tagCtx <input id="inp">INPUTINPUTmytaginnerinp0true'
+ " linkCtx ASCRIPTSCRIPTtruehtmlmytagAinline",
'tagCtx.ctxPrm(), linkCtx.attr etc etc', "tagCtx and linkCtx APIs tested, {{myTag ...}}...{{/myTag}}");
/*<<<<<<<<<<<<<*/ assert.testGroup("settings"); /*>>>>>>>>>>>>>*/
let delims = $.views.settings.delimiters();
let allowCode = $.views.settings.allowCode();
let trigger = $.views.settings.trigger();
$.views.settings.delimiters("<%", "%>", "&");
$.views.settings.allowCode(true);
$.views.settings.trigger(true);
test = "" + $.views.settings.delimiters() + $.views.settings.allowCode() + $.views.settings.trigger();
$.views.settings.delimiters(delims);
$.views.settings.allowCode(allowCode);
$.views.settings.trigger(trigger);
test += "" + $.views.settings.delimiters() + $.views.settings.allowCode() + $.views.settings.trigger();
assert.equal(test, "<%,%>,&truetrue{{,}},^falsefalse", "settings.delimiters()/allowCode()/trigger()", "get/set settings");
/*<<<<<<<<<<<<<*/ assert.testGroup("binding"); /*>>>>>>>>>>>>>*/
tmpl = $.templates('{{include ~person=person}}<input data-link="person.name" id="nmOuter"/>{^{for person}}{^{:name}}<div>{^{:~person.name}}{{if true}} <input data-link="name" id="nm"/> <input data-link="~person.name"/>{{/if}}</div>{{/for}}{{/include}}');
let data2 = {person: {name: "Jo"} };
$.link(tmpl, "#result", data2);
let view = $.view("#nm");
test = "" + (
view === $.view("#nm") &&
view === $.view($("#nm")) &&
view === $.view($("#nm")[0]) &&
view === $("#nm").view()
); // Different variants of $.view()
assert.equal(test, "true", "$.view()", "Variants of $.view() and $(...).view()");
view = $.view("#nm").parent;
test = "" + (
view === $.view("#nm", "for") &&
view === $("#nm").view("for") &&
view === $.view("#nm").get("for") &&
view === $("#nm").view().get("for")
); // Different variants of $.view(type)
view = $.view("#nm").root;
test += "" + (view === $.view("#nm", view.type))
+ "" + (view === $.view("#nm", "root"));
assert.equal(test, "truetruetrue", "$.view(type)", "Variants of $.view(..., type) and $(...).view(type)");
view = $.view("#result", true, "for");
test = "" + (
view === $.view("#result", true, "for") &&
view === $("#result").view(true, "for") &&
view === $("#result").view().get(true, "for") &&
view === $("#nmOuter").view().get(true, "for")
); // Different variants of $.view(true, type)
test += $("#nmOuter").view(true) === undefined;
test += $("#nmOuter").view().get(true).type;
assert.equal(test, "truetruefor", "$.view(type)", "Variants of $.view(..., true, type) and $(...).view(true, type)");
test = text();
$("#nmOuter").val("Bob").change();
test += text();
test += $.view("#nmOuter").type + $.view("#nm").type;
$.unlink("#result div");
$.unlink($("#result div")); // Variant
$.unlink($("#result div")[0]); // Variant
$("#nm").val("Jane").change();
$("#nmOuter").val("Fiona").change();
test += "| unlink: " + text();
test += $.view("#nmOuter").type + $.view("#nm").type;
$.link(tmpl, "#result", data2);
$("#nm").val("Carlos").change();
test += "| relink: " + text() + $.view("#nm").type;
$("#result div").unlink();
$("#nm").val("Henri").change();
test += "| unlink2: " + text() + $.view("#nm").type;
assert.equal(test, "JoJo BobBob includeif| unlink: FionaBob includefor| relink: CarlosCarlos if| unlink2: CarlosCarlos for",
"$.unlink()", "Data-linking, two-way, and unlink() variants");
$.link($.templates('{{:length}}{^{:#data[0].person.name}}'), "#result", [data2], {}, true);
$.observable(data2).setProperty({"person.name": "Maria"});
test += "| relink array noIteration: " + text();
assert.equal(test, "JoJo BobBob includeif| unlink: FionaBob includefor| relink: CarlosCarlos if| unlink2: CarlosCarlos for| relink array noIteration: 1Maria",
"$.link(tmpl ...), $.unlink()", "Data-linking, two-way, and link() / unlink() variants");
data2 = {person: {name: "Jo"} };
$("#result").html('<div data-link="person.name"></div><div data-link="~hlp"></div><input data-link="person.name"/>');
$.link(true, "#result", data2);
test = "link " + text();
$.observable(data2).setProperty({"person.name": "Fiona"});
test += text();
$("#result input").val("Henri").change();
test += text();
$.unlink();
test += " unlink ";
$("#result input").val("James").change();
test += text();
$.observable(data2).setProperty({"person.name": "Fiona"});
test += text();
$.link(true, "#result", data2, { hlp: "Helper" });
test += " link " + text();
$("#result div").unlink();
$("#result input").val("Canut").change();
test += data2.person.name + text();
$("#result").link(true, data2, { hlp: "Helper" }).link(true, data2, { hlp: "Helper2" });
test += text();
$.link(true, "#result", data2, { hlp: "Helper" }).link(true, data2, { hlp: "Helper3" });
test += text();
$.observable(data2).setProperty({"person.name": "Reggie"});
test += text();
assert.equal(test, "link JoHlpFionaHlpHenriHlp unlink HenriHlpHenriHlp link FionaHelperCanutFionaHelperCanutHelper2CanutHelper3ReggieHelper3",
"$.link(true ...), $.unlink()", "Data-linking, top-level, declarative");
$.unlink("#result");
$("#result").html('<div class="nm"></div><div class="hlp"></div><input class="nm"/>');
$.link("person.name", "#result .nm", data2, { hlp: "Helper" });
$.link("~hlp", "#result .hlp", data2, { hlp: "Helper" });
test = text();
$("#result input").val("Rick").change();
test += text();
$.observable(data2).setProperty({"person.name": "Paul"});
test += text();
$.unlink("#result");
test += " unlink ";
$("#result input").val("Josephine").change();
test += text();
$.observable(data2).setProperty("person.name", "Ray");
test += text();
assert.equal(test, "ReggieHelperRickHelperPaulHelper unlink PaulHelperPaulHelper",
"$.link(expr ...), $.unlink()", "Data-linking, top-level, programmatic");
/*<<<<<<<<<<<<<*/ assert.testGroup("Observable"); /*>>>>>>>>>>>>>*/
let data4 = {person: {name: "Jo", address: {street: "Main St"}, phones: [1, 2, 3], info: <JsViews.GetSet>null}};
data4.person.info = function(this: JsViews.Hash<any>) {
return "info: " + this.name + " " + this.address.street;
} as JsViews.GetSet;
data4.person.info.depends = function() {
return ["name", "address^street"];
};
tmpl = $.templates("{^{:person.name}} {^{:person.address.street}} {^{for person.phones}}{^{:}}{{/for}} {^{:person.info()}} {^{:~personinfo}} {^{:~personstuff()}}");
tmpl.link("#result", data4, {personinfo: "Joseph", personstuff: function() {
return "xxx";
}});
test = text();
$.observable(data4).setProperty("person.name", "Ray");
$.observable(data4).setProperty({"person.address.street": "Broadway"});
$.observable(data4.person.phones).insert(1, [5, 6]);
test += text();
$.observable(data4).setProperty({"person.address": {street: "Narrowway"}});
test += text();
$.observable(data4.person.phones).move(1, 2, 2);
test += text();
$.observable(data4.person.phones).remove(1);
test += text();
$.observable(data4.person.phones).refresh([4, 3, 2, 1]);
test += text();
$.view("#result", true, "item").ctxPrm("personinfo", "YYYY");
test += text();
assert.equal(test, "Jo Main St 123 info: Jo Main St Joseph xxxRay Broadway 15623"
+ " info: Ray Broadway Joseph xxxRay Broadway 15623"
+ " info: Ray Narrowway Joseph xxxRay Broadway 12563"
+ " info: Ray Narrowway Joseph xxxRay Broadway 1563"
+ " info: Ray Narrowway Joseph xxxRay Broadway 4321"
+ " info: Ray Narrowway Joseph xxxRay Broadway 4321"
+ " info: Ray Narrowway YYYY xxx",
"$.observable(...)", "Observable APIs");
function keydown(elem: any) {
if ("oninput" in document) {
elem.trigger("input");
} else {
elem.keydown();
}
}
var fullName = function(this: JsViews.Hash<string>, reversed: boolean) {
return reversed
? this.lastName + " " + this.firstName
: this.firstName + " " + this.lastName;
} as JsViews.GetSet;
var person = {
firstName: "Jeff",
lastName: "Smith",
fullName: fullName
};
fullName.depends = "*";
fullName.set = function(val: string) {
let vals = val.split(" ");
$.observable(this).setProperty({
lastName: vals.pop(),
firstName: vals.join(" ")
});
};
$.templates('{^{:firstName}} {^{:lastName}} {^{:fullName()}} {^{:fullName(true)}} <input id="full" data-link="fullName()"/>')
.link("#result", person);
// ................................ Act ..................................
test = text();
$.observable(person).setProperty({firstName: "newFirst", lastName: "newLast"});
test += text();
$.observable(person).setProperty({fullName: "compFirst compLast"});
test += text();
keydown($("#full").val("2wayFirst 2wayLast"));
test += text();
assert.equal(test, "Jeff Smith Jeff Smith Smith Jeff newFirst newLast newFirst newLast newLast newFirst compFirst compLast compFirst compLast compLast compFirst compFirst compLast compFirst compLast compLast compFirst ",
"fullName as JsViews.GetSet; fullName.depends/set etc", "GetSet computed function");
})();