71
this.set.remove.apply(this.set, arguments);
return this;
},
contains: function(v) { return this.set.contains(v); },
size: function() { return this.set.size(); },
foreach: function(f,c) { this.set.foreach(f,c); }
});
One of the benefits of using composition in this case is that only a single FilteredSet
subclass is required. Instances of this class can be created to restrict the membership
of any other set instance. Instead of using the NonNullSet class defined earlier, for
example, we can do this:
var s = new FilteredSet(new Set(), function(x) { return x !== null; });
We can even filter a filtered set:
var t = new FilteredSet(s, { function(x} { return !(x instanceof Set); });
9.7.4 Class Hierarchies and Abstract Classes
In the previous section you were urged to “favor composition over inheritance.” But to
illustrate this principle, we created a subclass of Set. We did this so that the resulting
class would be
instanceof Set
, and so that it could inherit the useful auxiliary Set
methods like
toString()
and
equals()
. These are valid pragmatic reasons, but it still
would have been nice to be able to do set composition without subclassing a concrete
implementation like the Set class. A similar point can be made about our SingletonSet
class from Example 9-12—that class subclassed Set, so that it could inherit the auxiliary
methods, but its implementation was completely different than its superclass.
SingletonSet is not a specialized version of the Set class, but a completely different kind
of Set. SingletonSet should be a sibling of Set in the class hierarchy, not a descendant
of it.
The solution in classical OO languages and also in JavaScript is to separate interface
from implementation. Suppose we define an AbstractSet class which implements the
auxiliary methods like
toString()
but does not implement the core methods like
foreach()
. Then, our set implementations, Set, SingletonSet, and FilteredSet, can all
be subclasses of AbstractSet. FilteredSet and SingletonSet no longer subclass an unre-
lated implementation.
Example 9-16 takes this approach further and defines a hierarchy of abstract set classes.
AbstractSet defines only a single abstract method,
contains()
. Any class that purports
to be a set must define at least this one method. Next, we subclass AbstractSet to define
AbstractEnumerableSet. That class adds abstract
size()
and
foreach()
methods, and
defines useful concrete methods (
toString()
,
toArray()
,
equals()
, and so on) on top
of them. AbstractEnumerableSet does not define
add()
or
remove()
methods and rep-
resents read-only sets. SingletonSet can be implemented as a concrete subclass. Finally,
we define AbstractWritableSet as a subclass of AbstractEnumerableSet. This final ab-
stract set defines the abstract methods
add()
and
remove()
, and implements concrete
234 | Chapter 9: Classes and Modules
58
methods like
union()
and
intersection()
that use them.
AbstractWritableSet
is the
appropriate superclass for our Set and FilteredSet classes. They are omitted from this
example, however, and a new concrete implementation named ArraySet is included
instead.
Example 9-16 is a long example, but worth reading through in its entirety. Note that
it uses
Function.prototype.extend()
as a shortcut for creating subclasses.
Example 9-16. A hierarchy of abstract and concrete Set classes
// A convenient function that can be used for any abstract method
function abstractmethod() { throw new Error("abstract method"); }
/*
* The AbstractSet class defines a single abstract method, contains().
*/
function AbstractSet() { throw new Error("Can't instantiate abstract classes");}
AbstractSet.prototype.contains = abstractmethod;
/*
* NotSet is a concrete subclass of AbstractSet.
* The members of this set are all values that are not members of some
* other set. Because it is defined in terms of another set it is not
* writable, and because it has infinite members, it is not enumerable.
* All we can do with it is test for membership.
* Note that we're using the Function.prototype.extend() method we defined
* earlier to define this subclass.
*/
var NotSet = AbstractSet.extend(
function NotSet(set) { this.set = set; },
{
contains: function(x) { return !this.set.contains(x); },
toString: function(x) { return "~" + this.set.toString(); },
equals: function(that) {
return that instanceof NotSet && this.set.equals(that.set);
}
}
);
/*
* AbstractEnumerableSet is an abstract subclass of AbstractSet.
* It defines the abstract methods size() and foreach(), and then implements
* concrete isEmpty(), toArray(), to[Locale]String(), and equals() methods
* on top of those. Subclasses that implement contains(), size(), and foreach()
* get these five concrete methods for free.
*/
var AbstractEnumerableSet = AbstractSet.extend(
function() { throw new Error("Can't instantiate abstract classes"); },
{
size: abstractmethod,
foreach: abstractmethod,
isEmpty: function() { return this.size() == 0; },
toString: function() {
var s = "{", i = 0;
9.7 Subclasses | 235
Core JavaScript
54
this.foreach(function(v) {
if (i++ > 0) s += ", ";
s += v;
});
return s + "}";
},
toLocaleString : function() {
var s = "{", i = 0;
this.foreach(function(v) {
if (i++ > 0) s += ", ";
if (v == null) s += v; // null & undefined
else s += v.toLocaleString(); // all others
});
return s + "}";
},
toArray: function() {
var a = [];
this.foreach(function(v) { a.push(v); });
return a;
},
equals: function(that) {
if (!(that instanceof AbstractEnumerableSet)) return false;
// If they don't have the same size, they're not equal
if (this.size() != that.size()) return false;
// Now check whether every element in this is also in that.
try {
this.foreach(function(v) {if (!that.contains(v)) throw false;});
return true; // All elements matched: sets are equal.
} catch (x) {
if (x === false) return false; // Sets are not equal
throw x; // Some other exception occurred: rethrow it.
}
}
});
/*
* SingletonSet is a concrete subclass of AbstractEnumerableSet.
* A singleton set is a read-only set with a single member.
*/
var SingletonSet = AbstractEnumerableSet.extend(
function SingletonSet(member) { this.member = member; },
{
contains: function(x) { return x === this.member; },
size: function() { return 1; },
foreach: function(f,ctx) { f.call(ctx, this.member); }
}
);
/*
* AbstractWritableSet is an abstract subclass of AbstractEnumerableSet.
* It defines the abstract methods add() and remove(), and then implements
* concrete union(), intersection(), and difference() methods on top of them.
*/
var AbstractWritableSet = AbstractEnumerableSet.extend(
function() { throw new Error("Can't instantiate abstract classes"); },
236 | Chapter 9: Classes and Modules
How to C#: Basic SDK Concept of XDoc.PDF for .NET XDoc.PDF for .NET allows C# developers to edit hyperlink of PDF document, including editing PDF url links and quick navigation link in bookmark/outline.
adding hyperlinks to pdf documents; add links to pdf acrobat
55
{
add: abstractmethod,
remove: abstractmethod,
union: function(that) {
var self = this;
that.foreach(function(v) { self.add(v); });
return this;
},
intersection: function(that) {
var self = this;
this.foreach(function(v) { if (!that.contains(v)) self.remove(v);});
return this;
},
difference: function(that) {
var self = this;
that.foreach(function(v) { self.remove(v); });
return this;
}
});
/*
* An ArraySet is a concrete subclass of AbstractWritableSet.
* It represents the set elements as an array of values, and uses a linear
* search of the array for its contains() method. Because the contains()
* method is O(n) rather than O(1), it should only be used for relatively
* small sets. Note that this implementation relies on the ES5 Array methods
* indexOf() and forEach().
*/
var ArraySet = AbstractWritableSet.extend(
function ArraySet() {
this.values = [];
this.add.apply(this, arguments);
},
{
contains: function(v) { return this.values.indexOf(v) != -1; },
size: function() { return this.values.length; },
foreach: function(f,c) { this.values.forEach(f, c); },
add: function() {
for(var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
if (!this.contains(arg)) this.values.push(arg);
}
return this;
},
remove: function() {
for(var i = 0; i < arguments.length; i++) {
var p = this.values.indexOf(arguments[i]);
if (p == -1) continue;
this.values.splice(p, 1);
}
return this;
}
}
);
9.7 Subclasses | 237
Core JavaScript
VB.NET PDF: Basic SDK Concept of XDoc.PDF XDoc.PDF for .NET allows VB.NET developers to edit hyperlink of PDF document, including editing PDF url links and quick navigation link in bookmark/outline.
pdf links; add a link to a pdf in acrobat
48
9.8 Classes in ECMAScript 5
ECMAScript 5 adds methods for specifying property attributes (getters, setters, enu-
merability, writability, and configurability) and for restricting the extensibility of ob-
jects. These methods were described in §6.6, §6.7, and §6.8.3, but turn out to be quite
useful when defining classes. The subsections that follow demonstrate how to use these
ECMAScript 5 capabilities to make your classes more robust.
9.8.1 Making Properties Nonenumerable
The Set class of Example 9-6 used a trick to store objects as set members: it defined an
“object id” property on any object added to the set. Later, if other code uses that object
in a
for/in
loop, this added property will be returned. ECMAScript 5 allows us to avoid
this by making properties nonenumerable. Example 9-17 demonstrates how to do this
with
Object.defineProperty()
and also shows how to define a getter function and how
to test whether an object is extensible.
Example 9-17. Defining nonenumerable properties
// Wrap our code in a function so we can define variables in the function scope
(function() {
// Define objectId as a nonenumerable property inherited by all objects.
// When this property is read, the getter function is invoked.
// It has no setter, so it is read-only.
// It is nonconfigurable, so it can't be deleted.
Object.defineProperty(Object.prototype, "objectId", {
get: idGetter, // Method to get value
enumerable: false, // Nonenumerable
configurable: false // Can't delete it
});
// This is the getter function called when objectId is read
function idGetter() { // A getter function to return the id
if (!(idprop in this)) { // If object doesn't already have an id
if (!Object.isExtensible(this)) // And if we can add a property
throw Error("Can't define id for nonextensible objects");
Object.defineProperty(this, idprop, { // Give it one now.
value: nextid++, // This is the value
writable: false, // Read-only
enumerable: false, // Nonenumerable
configurable: false // Nondeletable
});
}
return this[idprop]; // Now return the existing or new value
};
// These variables are used by idGetter() and are private to this function
var idprop = "|**objectId**|"; // Assume this property isn't in use
var nextid = 1; // Start assigning ids at this #
}()); // Invoke the wrapper function to run the code right away
238 | Chapter 9: Classes and Modules
59
9.8.2 Defining Immutable Classes
In addition to making properties nonenumerable, ECMAScript 5 allows us to make
properties read-only, which is handy if we want to define classes whose instances are
immutable. Example 9-18 is an immutable version of our Range class that does
this using
Object.defineProperties()
and with
Object.create()
. It also uses
Object.defineProperties()
to set up the prototype object for the class, making the
instance methods nonenumerable, like the methods of built-in classes. In fact, it goes
further than this and makes those instance methods read-only and nondeletable, which
prevents any dynamic alterations (“monkey-patching”) to the class. Finally, as an in-
teresting trick, Example 9-18 has a constructor function that works as a factory function
when invoked without the
new
keyword.
Example 9-18. An immutable class with read-only properties and methods
// This function works with or without 'new': a constructor and factory function
function Range(from,to) {
// These are descriptors for the read-only from and to properties.
var props = {
from: {value:from, enumerable:true, writable:false, configurable:false},
to: {value:to, enumerable:true, writable:false, configurable:false}
};
if (this instanceof Range) // If invoked as a constructor
Object.defineProperties(this, props); // Define the properties
else // Otherwise, as a factory
return Object.create(Range.prototype, // Create and return a new
props); // Range object with props
}
// If we add properties to the Range.prototype object in the same way,
// then we can set attributes on those properties. Since we don't specify
// enumerable, writable, or configurable, they all default to false.
Object.defineProperties(Range.prototype, {
includes: {
value: function(x) { return this.from <= x && x <= this.to; }
},
foreach: {
value: function(f) {
for(var x = Math.ceil(this.from); x <= this.to; x++) f(x);
}
},
toString: {
value: function() { return "(" + this.from + "..." + this.to + ")"; }
}
});
Example 9-18 uses
Object.defineProperties()
and
Object.create()
to define immut-
able and nonenumerable properties. These are powerful methods, but the property
descriptor objects they require can make the code difficult to read. An alternative is to
define utility functions for modifying the attributes of properties that have already been
defined. Example 9-19 shows two such utility functions.
9.8 Classes in ECMAScript 5 | 239
Core JavaScript
55
Example 9-19. Property descriptor utilities
// Make the named (or all) properties of o nonwritable and nonconfigurable.
function freezeProps(o) {
var props = (arguments.length == 1) // If 1 arg
? Object.getOwnPropertyNames(o) // use all props
: Array.prototype.splice.call(arguments, 1); // else named props
props.forEach(function(n) { // Make each one read-only and permanent
// Ignore nonconfigurable properties
if (!Object.getOwnPropertyDescriptor(o,n).configurable) return;
Object.defineProperty(o, n, { writable: false, configurable: false });
});
return o; // So we can keep using it
}
// Make the named (or all) properties of o nonenumerable, if configurable.
function hideProps(o) {
var props = (arguments.length == 1) // If 1 arg
? Object.getOwnPropertyNames(o) // use all props
: Array.prototype.splice.call(arguments, 1); // else named props
props.forEach(function(n) { // Hide each one from the for/in loop
// Ignore nonconfigurable properties
if (!Object.getOwnPropertyDescriptor(o,n).configurable) return;
Object.defineProperty(o, n, { enumerable: false });
});
return o;
}
Object.defineProperty()
and
Object.defineProperties()
can be used to create new
properties and also to modify the attributes of existing properties. When used to define
new properties, any attributes you omit default to
false
. When used to alter existing
properties, however, the attributes you omit are left unchanged. In the
hideProps()
function above, for example, we specify only the
enumerable
attribute because that is
the only one we want to modify.
With these utility functions defined, we can take advantage of ECMAScript 5 features
to write an immutable class without dramatically altering the way we write classes.
Example 9-20 shows an immutable Range class that uses our utility functions.
Example 9-20. A simpler immutable class
function Range(from, to) { // Constructor for an immutable Range class
this.from = from;
this.to = to;
freezeProps(this); // Make the properties immutable
}
Range.prototype = hideProps({ // Define prototype with nonenumerable properties
constructor: Range,
includes: function(x) { return this.from <= x && x <= this.to; },
foreach: function(f) {for(var x=Math.ceil(this.from);x<=this.to;x++) f(x);},
toString: function() { return "(" + this.from + "..." + this.to + ")"; }
});
240 | Chapter 9: Classes and Modules
51
9.8.3 Encapsulating Object State
§9.6.6 and Example 9-10 showed how you can use variables or arguments of a con-
structor function as private state for the objects created by that constructor. The short-
coming of this technique is that in ECMAScript 3, the accessor methods that provide
access to that state can be replaced. ECMAScript 5 allows us to encapsulate our state
variables more robustly by defining property getter and setter methods that cannot be
deleted. Example 9-21 demonstrates.
Example 9-21. A Range class with strongly encapsulated endpoints
// This version of the Range class is mutable but encapsulates its endpoint
// variables to maintain the invariant that from <= to.
function Range(from, to) {
// Verify that the invariant holds when we're created
if (from > to) throw new Error("Range: from must be <= to");
// Define the accessor methods that maintain the invariant
function getFrom() { return from; }
function getTo() { return to; }
function setFrom(f) { // Don't allow from to be set > to
if (f <= to) from = f;
else throw new Error("Range: from must be <= to");
}
function setTo(t) { // Don't allow to to be set < from
if (t >= from) to = t;
else throw new Error("Range: to must be >= from");
}
// Create enumerable, nonconfigurable properties that use the accessors
Object.defineProperties(this, {
from: {get: getFrom, set: setFrom, enumerable:true, configurable:false},
to: { get: getTo, set: setTo, enumerable:true, configurable:false }
});
}
// The prototype object is unchanged from previous examples.
// The instance methods read from and to as if they were ordinary properties.
Range.prototype = hideProps({
constructor: Range,
includes: function(x) { return this.from <= x && x <= this.to; },
foreach: function(f) {for(var x=Math.ceil(this.from);x<=this.to;x++) f(x);},
toString: function() { return "(" + this.from + "..." + this.to + ")"; }
});
9.8.4 Preventing Class Extensions
It is usually considered a feature of JavaScript that classes can be dynamically extended
by adding new methods to the prototype object. ECMAScript 5 allows you to prevent
this, if you want to.
Object.preventExtensions()
makes an object nonextensible
(§6.8.3), which means that no new properties can be added to it.
Object.seal()
takes
this a step further: it prevents the addition of new properties and also makes all current
properties nonconfigurable, so that they cannot be deleted. (A nonconfigurable
9.8 Classes in ECMAScript 5 | 241
Core JavaScript
75
property can still be writable, however, and can still be converted into a read-only
property.) To prevent extensions to
Object.prototype
, you can simply write:
Object.seal(Object.prototype);
Another dynamic feature of JavaScript is the ability to replace (or “monkey-patch”)
methods of an object:
var original_sort_method = Array.prototype.sort;
Array.prototype.sort = function() {
var start = new Date();
original_sort_method.apply(this, arguments);
var end = new Date();
console.log("Array sort took " + (end - start) + " milliseconds.");
};
You can prevent this kind of alteration by making your instance methods read-only.
The
freezeProps()
utility function defined above is one way to accomplish this. An-
other way is with
Object.freeze()
, which does everything that
Object.seal()
does, but
also makes all properties read-only and nonconfigurable.
There is a feature of read-only properties that is important to understand when
working with classes. If an object
o
inherits a read-only property
p
, an attempt to assign
to
o.p
will fail and will not create a new property in
o
. If you want to override an
inherited read-only property, you have to use
Object.defineProperty()
or
Object.defineProperties()
or
Object.create()
to create the new property. This means
that if you make the instance methods of a class read-only, it becomes significantly
more difficult for subclasses to override those methods.
It is not usually necessary to lock down prototype objects like this, but there are some
circumstances where preventing extensions to an object can be useful. Think back to
the
enumeration()
class factory function of Example 9-7. That function stored the in-
stances of each enumerated type in properties of the constructor object, and also in the
values
array of the constructor. These properties and array serve as the official list of
instances of the enumerated type, and it is worth freezing them, so that new instances
cannot be added and existing instances cannot be deleted or altered. In the
enumeration()
function we can simply add these lines of code:
Object.freeze(enumeration.values);
Object.freeze(enumeration);
Notice that by calling
Object.freeze()
on the enumerated type, we prevent the future
use of the
objectId
property defined in Example 9-17. A solution to this problem is to
read the
objectId
property (calling the underlying accessor method and setting the
internal property) of the enumerated type once before freezing it.
9.8.5 Subclasses and ECMAScript 5
Example 9-22 demonstrates subclassing using ECMAScript 5 features. It defines a
StringSet class as a subclass of the AbstractWritableSet class from Example 9-16. The
main feature of this example is the use of
Object.create()
to create a prototype object
242 | Chapter 9: Classes and Modules
Documents you may be interested
- open pdf and draw c#: Add hyperlinks pdf file control SDK platform web page .net html web browser xquery-tutorial6-part1498
- open pdf and draw c#: Add links to pdf file software Library dll windows .net html web forms xquery-tutorial7-part1499
- open pdf and draw c#: C# read pdf from url application SDK cloud windows wpf asp.net class xquery-tutorial8-part1500
- Pdf hyperlinks xquery-tutorial9-part1501
- open pdf and draw c#: Adding a link to a pdf in preview application software utility azure html winforms visual studio The-Great-Gatsby1-part17
- open pdf and draw c#: Add hyperlink to pdf in software Library cloud windows .net web page class TMS%20TAdvStringGrid%20Developers%20Guide4-part152
- Pdf hyperlink Yl23Al9Pz9PA3NJvumF35vHcWw2IHEtwhFknsYdWWNVjovdc0-part1504
- open pdf and draw c#: Convert excel to pdf with hyperlinks Library SDK class asp.net wpf azure ajax Yl23Al9Pz9PA3NJvumF35vHcWw2IHEtwhFknsYdWWNVjovdc1-part1505
- open pdf and draw c#: Add hyperlink to pdf online control Library platform web page .net azure web browser Yl23Al9Pz9PA3NJvumF35vHcWw2IHEtwhFknsYdWWNVjovdc2-part1506
- open pdf and draw c#: Add a link to a pdf file application Library utility html .net winforms visual studio Yl23Al9Pz9PA3NJvumF35vHcWw2IHEtwhFknsYdWWNVjovdc3-part1507
- open pdf and draw c#: Add hyperlink to pdf control SDK system web page wpf azure console Yl23Al9Pz9PA3NJvumF35vHcWw2IHEtwhFknsYdWWNVjovdc4-part1508
- open pdf and draw c#: Add links to pdf software Library dll winforms .net azure web forms Yl23Al9Pz9PA3NJvumF35vHcWw2IHEtwhFknsYdWWNVjovdc5-part1509
- Pdf links Yl23Al9Pz9PA3NJvumF35vHcWw2IHEtwhFknsYdWWNVjovdc6-part1510
- open pdf and draw c#: Convert excel to pdf with hyperlinks control application utility azure web page html visual studio Yl23Al9Pz9PA3NJvumF35vHcWw2IHEtwhFknsYdWWNVjovdc7-part1511
- open pdf and draw c#: Clickable links in pdf from word control application utility azure web page html visual studio TMS%20TAdvStringGrid%20Developers%20Guide5-part153
- open pdf and draw c#: Add hyperlinks pdf file application SDK tool html .net winforms online Yl23Al9Pz9PA3NJvumF35vHcWw2IHEtwhFknsYdWWNVjovdc8-part1512
- open pdf and draw c#: Add hyperlink to pdf in preview Library control class asp.net web page windows ajax YSB_Install_Guide0-part1513
- open pdf and draw c#: Add hyperlink pdf SDK application project wpf html web page UWP ZF6UserGuide0-part1515
- open pdf and draw c#: Add hyperlink in pdf SDK application project wpf html web page UWP ZoomNotes60-part1517
- open pdf and draw c#: Add hyperlink to pdf acrobat SDK Library API .net asp.net windows sharepoint ZoomNotes61-part1518
- open pdf and draw c#: Add hyperlink to pdf in SDK Library API .net asp.net windows sharepoint ZoomNotes62-part1519
- open pdf and draw c#: Add hyperlink to pdf online software control cloud windows azure winforms class ZoomNotes63-part1520
- open pdf and draw c#: Adding links to pdf document control SDK platform web page winforms html web browser ZoomNotes64-part1521
- Pdf reader link TMS%20TAdvStringGrid%20Developers%20Guide6-part154
- Pdf hyperlinks ZoomNotes65-part1522
- open pdf and draw c#: Add links to pdf file Library application component asp.net windows winforms mvc zywall2_v3.60_QuickStartGuide0-part1526
- open pdf and draw c#: Add a link to a pdf file control SDK system azure winforms web page console %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D0-part1527
- open pdf and draw c#: Add hyperlink pdf document control SDK system azure winforms web page console %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D1-part1528
- open pdf and draw c#: Add url pdf control Library system azure asp.net web page console %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D10-part1529
- open pdf and draw c#: Chrome pdf from link SDK software project winforms wpf web page UWP %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D100-part1530
- open pdf and draw c#: Add links to pdf application Library utility html asp.net .net visual studio %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D101-part1531
- open pdf and draw c#: Add links to pdf in preview application Library utility html asp.net .net visual studio TMS%20TAdvStringGrid%20Developers%20Guide7-part155
- open pdf and draw c#: Add links to pdf document Library control component asp.net azure wpf mvc %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D102-part1532
- open pdf and draw c#: Add url pdf control Library platform web page .net html web browser %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D103-part1533
- open pdf and draw c#: Add links pdf document control Library platform web page .net html web browser %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D104-part1534
- open pdf and draw c#: Add email link to pdf SDK control service wpf azure web page dnn %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D105-part1535
- open pdf and draw c#: Add links to pdf file SDK Library service wpf asp.net html dnn %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D106-part1536
- Pdf edit hyperlink %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D107-part1537
- open pdf and draw c#: Change link in pdf application SDK utility azure wpf html visual studio %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D108-part1538
- Add email link to pdf %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D11-part1539
- Adding a link to a pdf %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D12-part1540
- C# read pdf from url %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D13-part1541
- Convert a word document to pdf with hyperlinks TMS%20TAdvStringGrid%20Developers%20Guide8-part156
- Add hyperlink pdf file %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D14-part1542
- Add hyperlink in pdf %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D15-part1543
- Adding an email link to a pdf %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D16-part1544
- Add a link to a pdf file %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D17-part1545
- Pdf edit hyperlink %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D18-part1546
- Add hyperlinks to pdf online %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D19-part1547
- Add a link to a pdf file %5BO%60Reilly%5D%20-%20JavaScript.%20The%20Definitive%20Guide,%206th%20ed.%20-%20%5BFlanagan%5D2-part1548
Documents you may be interested