JavaScript Object Creation Patterns
These samples demonstrate four different patterns for creating objects in JavaScript.
These samples demonstrate four different ways of creating objects in JavaScript:
1. Object factory
2. Objects linking to other objects (OLOO)
3. Pseudo-classical object pattern
4. ES6 classes
Object Factory Pattern
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
function createStudent(name, year) { let courses = []; let notes = {}; const getCourse = (key) => { return courses.find(obj => String(obj.code) === String(key)); }; return { info() { console.log(`${name} is a ${year} year student.`); }, listCourses() { console.log(courses); }, addCourse(course) { courses.push(course); }, addNote(code, text) { if (Object.keys(notes).includes(String(code))) { notes[code] += `; ${text}`; } else { if (getCourse(code)) notes[code] = text; } }, updateNote(code, text) { if (Object.keys(notes).includes(String(code))) notes[code] = text; }, viewNotes() { for (key in notes) { let courseName = getCourse(key).name; console.log(courseName + ':', notes[key]); } }, } } |
OLOO Pattern
This example also shows the use of WeakMap in an IIFE to hold private instance data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
const Account = (function() { const PWDERRMSG = 'Invalid Password'; const Private = new WeakMap(); function makeDisplayName() { let displayName = ''; let randASCII; for (let i = 0; i < 16; i++) { randASCII = Math.floor(Math.random() * 94) + 32; displayName += String.fromCharCode(randASCII); } return displayName; } function pwdCheck(password, pvtObj) { return password === pvtObj.password; } return { init(email, password, firstName, lastName) { Private.set(this, { email, password, firstName, lastName, }); this.displayName = makeDisplayName(); return this; }, email(password) { const pvtObj = Private.get(this); return pwdCheck(password, pvtObj) ? pvtObj.email : PWDERRMSG; }, firstName(password) { const pvtObj = Private.get(this); return pwdCheck(password, pvtObj) ? pvtObj.firstName : PWDERRMSG; }, lastName(password) { const pvtObj = Private.get(this); return pwdCheck(password, pvtObj) ? pvtObj.lastName : PWDERRMSG; }, resetPassword(password, newPassword) { const pvtObj = Private.get(this); if (pwdCheck(password, pvtObj)) { pvtObj.password = newPassword; return true; } return PWDERRMSG; }, reanonymize(password) { const pvtObj = Private.get(this); if (pwdCheck(password, pvtObj)) { this.displayName = makeDisplayName(); return true; } return PWDERRMSG; } } })(); |
Pseudo-Classical Pattern
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
function Person(firstName, lastName, age, gender) { this.firstName = firstName; this.lastName = lastName; this.age = age; this.gender = gender; } Person.prototype.communicate = function() { console.log('Communicating'); const test = () => console.log(this); test(); } Person.prototype.eat = function() { console.log('Eating'); } Person.prototype.sleep = function() { console.log('Sleeping'); } Person.prototype.fullName = function() { return this.firstName + ' ' + this.lastName; } function Doctor(firstName, lastName, age, gender, specialization) { Person.call(this, firstName, lastName, age, gender); this.specialization = specialization; } Doctor.prototype = Object.create(Person.prototype); // Doctor.prototype.constructor = Doctor; Object.defineProperty(Doctor.prototype, 'constructor', { value: Doctor, enumerable: false, writable: true }); Doctor.prototype.diagnose = function() { console.log('Diagnosing'); } function Professor(firstName, lastName, age, gender, subject) { Person.call(this, firstName, lastName, age, gender); this.subject = subject; } Professor.prototype = Object.create(Person.prototype); Professor.prototype.constructor = Professor; Professor.prototype.teach = function() { console.log('Teaching'); } function Student(firstName, lastName, age, gender, degree) { Person.call(this, firstName, lastName, age, gender); this.degree = degree; } Student.prototype = Object.create(Person.prototype); Student.prototype.constructor = Student; Student.prototype.study = function() { console.log('Studying'); } function GraduateStudent(firstName, lastName, age, gender, degree, graduateDegree) { Student.call(this, firstName, lastName, age, gender, degree); this.graduateDegree = graduateDegree; } GraduateStudent.prototype = Object.create(Student.prototype); GraduateStudent.prototype.constructor = GraduateStudent; GraduateStudent.prototype.research = function() { console.log('Researching'); } |
ES6 Class
Private constants are not directly supported by ES6 class syntax. The way to create a private constant is to create a private getter method and return the constant value, as shown here in #PWDERRMSG.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
class Account { #email; #password; #firstName; #lastName; constructor(email, password, firstName, lastName) { this.#email = email; this.#password = password; this.#firstName = firstName; this.#lastName = lastName; this.displayName = this.#makeDisplayName(); } get #PWDERRMSG() { return 'Invalid Password'; } #makeDisplayName() { let displayName = ''; let randASCII; for (let i = 0; i < 16; i++) { randASCII = Math.floor(Math.random() * 94) + 32; displayName += String.fromCharCode(randASCII); } return displayName; } #pwdCheck(password) { return password === this.#password; } email(password) { return this.#pwdCheck(password) ? this.#email : this.#PWDERRMSG; } firstName(password) { return this.#pwdCheck(password) ? this.#firstName : this.#PWDERRMSG; } lastName(password) { return this.#pwdCheck(password) ? this.#lastName : this.#PWDERRMSG; } resetPassword(password, newPassword) { if (this.#pwdCheck(password)) { this.#password = newPassword; return true; } return this.#PWDERRMSG; } reanonymize(password) { if (this.#pwdCheck(password)) { this.displayName = this.#makeDisplayName(); return true; } return this.#PWDERRMSG; } } |
React/JavaScript: Tic-Tac-Toe Game (Play game)
This is a tic-tac-toe game, written in JavaScript and React.
This is a tic-tac-toe game, written in JavaScript and React. It uses create-react-app to create a React application. The listing below is App.js.
App.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
import { useState } from 'react'; const WINNING_LINES = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6] ] const PLAYER_MARKER = 'X'; const COMPUTER_MARKER = 'O'; // React components // Game component, handles interaction between Board and ComputerStrength components export default function Game() { const [strength, setStrength] = useState('Unbeatable'); const handleRadio = (e) => { setStrength(e.target.value); } return ( <div className="game"> <div className="header"><h2>Tic-Tac-Toe</h2></div> <div className="game-board"> <Board strength={strength} /> </div> <div className="computer-strength-container"> <ComputerStrength className="strength" value={'Computer Strength'} strength={strength} onRadioChange={(e) => handleRadio(e)} /> </div> </div> ) } // Board component function Board({strength}) { const [squares, setSquares] = useState(Array(9).fill(null)); let status; let winner = checkWinner(squares); if (winner) { status = `Winner: ${winner}`; showResetButton(); } else if (squares.some(s => s === null)) { status = `Click a square ...`; } else { status = `Draw`; showResetButton(); } const handleClick = i => { if (squares[i] || winner) return; const nextSquares = [...squares]; nextSquares[i] = PLAYER_MARKER; winner = checkWinner(nextSquares); if (!winner) { nextSquares[getComputerMove(nextSquares, strength)] = COMPUTER_MARKER; } setSquares(nextSquares); } const handleReset = () => { setSquares(Array(9).fill(null)); showResetButton(false); } return ( <> <div className="board"> <div className="status">{status}</div> <div className="board-row"> <Square value={squares[0]} onSquareClick={() => handleClick(0)} /> <Square value={squares[1]} onSquareClick={() => handleClick(1)} /> <Square value={squares[2]} onSquareClick={() => handleClick(2)} /> </div> <div className="board-row"> <Square value={squares[3]} onSquareClick={() => handleClick(3)} /> <Square value={squares[4]} onSquareClick={() => handleClick(4)} /> <Square value={squares[5]} onSquareClick={() => handleClick(5)} /> </div> <div className="board-row"> <Square value={squares[6]} onSquareClick={() => handleClick(6)} /> <Square value={squares[7]} onSquareClick={() => handleClick(7)} /> <Square value={squares[8]} onSquareClick={() => handleClick(8)} /> </div> </div> <div className="reset-container"> <Button className="reset" value="New Game" onButtonClick={() => handleReset()} /> </div> </> ); } // Square component function Square({value, onSquareClick}) { return ( <button className="square" onClick={onSquareClick} > {value} </button> ); } // Generic button component, used for "New Game" button function Button({value, onButtonClick}) { return ( <button className="reset" onClick={onButtonClick}>{value}</button> ) } // Computer strength component function ComputerStrength({value, strength, onRadioChange}) { return ( <div className="computer-strength"> <p>{value}</p> <label> <input type="radio" name="strength" value="Unbeatable" id="unbeatable" checked={strength === "Unbeatable"} onChange={onRadioChange} /> Unbeatable </label> <label> <input type="radio" name="strength" value="Decent" id="decent" checked={strength === "Decent"} onChange={onRadioChange} /> Decent </label> <label> <input type="radio" name="strength" value="Pitiful" id="pitiful" checked={strength === "Pitiful"} onChange={onRadioChange} /> Pitiful </label> </div> ) } // Helper functions function showResetButton(show = true) { let resetStyle = document.querySelector('.reset-container').style resetStyle.visibility = show ? 'visible' : 'hidden'; } function checkWinner(squares) { for (let line of WINNING_LINES) { const lineVals = getLineVals(squares, line); if (lineVals[0] && new Set(lineVals).size === 1) return lineVals[0]; } return null; } function getLineVals(squares, line) { return line.map(squareIndex => squares[squareIndex]); } // Computer move functions // This is a port from Ruby. In the Ruby version, each // of these method calls would return a value from 0 through 8 // or nil. Since 0 is truthy in Ruby, the logical or is a good // way to run through the methods until one returns a value // other than nil. But JS treats 0 as falsy, so the || construct // messes up if the function returns a 0 (top left corner move). // Adding 1 to the return value and then subtracting it back // out again works around this, although we have to also return // undefined instead of null. Because null + 1 == 1 in JS, and // undefined + 1 == NaN. function getComputerMove(squares, strength) { if (strength === 'Pitiful') return randomMove(squares); let move = takeComputerWin(squares) + 1 || blockPlayerWin(squares) + 1 || takeCenter(squares) + 1 || (strength === 'Unbeatable' ? cornerPlay(squares) + 1 : NaN) || randomMove(squares) + 1; return move - 1; } // Checks for winning computer move and returns it if there is one. function takeComputerWin(squares) { for (let line of WINNING_LINES) { const lineVals = getLineVals(squares, line); const computerSquares = lineVals.filter(sq => sq === COMPUTER_MARKER); if (computerSquares.length === 2 && lineVals.includes(null)) { return line[lineVals.findIndex(val => val === null)]; } } return undefined; } // Checks for winning player move and returns it if there is one // (computer will block the win). function blockPlayerWin(squares) { for (let line of WINNING_LINES) { const lineVals = getLineVals(squares, line); const playerSquares = lineVals.filter(sq => sq === PLAYER_MARKER); if (playerSquares.length === 2 && lineVals.includes(null)) { return line[lineVals.findIndex(val => val === null)]; } } return undefined; } // If no winning moves to take or block, take the center function takeCenter(squares) { return !squares[4] ? 4 : undefined; } // Blocks various winning techniques involving corners. Comes into play // if there are no winning moves to take or block, and center square is // taken. // // Not used unless computer strength is set to "Unbeatable." // // 1. If player takes two opposite corners, take any non-corner square. // 2. If player takes squares 5 and 7 for first two moves (and computer // takes center for first move), take any corner but the top left one. // 3. Otherwise, take first available corner (in the order 0, 2, 6, 8). // function cornerPlay(squares) { if ( [squares[0], squares[8]].every(sq => sq === PLAYER_MARKER) || [squares[2], squares[6]].every(sq => sq === PLAYER_MARKER) ) { return [1, 3, 5, 7].find(i => squares[i] === null); } else if (squares[5] === PLAYER_MARKER && squares[7] === PLAYER_MARKER) { return [2, 6, 8].find(i => squares[i] === null); } else { return [0, 2, 6, 8].find(i => squares[i] === null); } return undefined; } // Takes any available square. If none of the other functions returns a move, // this is the default. Also, this is the only function called if computer // strength is set to "Pitiful." function randomMove(squares) { const openSquares = []; squares.forEach((e, i) => { if (e === null) openSquares.push(i); }); return openSquares[Math.floor(Math.random() * openSquares.length)]; } |
Ruby: Blackjack Game
This is a terminal-based version of Blackjack, written in Ruby.
This is a terminal-based version of Blackjack, written in Ruby.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 |
# Application-wide constants module App BUST_VALUE = 21 DEALER_STAND_VALUE = 17 SCREEN_WIDTH = 60 HEADER = "n#{'Rodes Black Jack'.center(SCREEN_WIDTH)}nn" SLEEP_TIME = 1 # How long to wait between dealer cards dealt, etc. end module Utilities require 'io/console' KEY_ONLY = true # Key only or key + enter def getchar if KEY_ONLY char = STDIN.getch if char == "u0003" # Ctrl-C puts exit end else char = gets.chomp[0] end char end def cls system('clear') || system('cls') end def say(message, new_lines = 0) print message.to_s print "n" * new_lines end # This disables keyboard input while #sleep is running. Necessary in # key_only mode because otherwise key inputs will echo on the screen. def wait(for_time) system('stty raw -echo') sleep for_time system('stty -raw echo') end # Assumes "space plus nn" for new paragraph. def word_wrap(str, width = App::SCREEN_WIDTH) char_count = 0 lastchar = str.end_with?(' ') ? ' ' : '' str.split(/ /).each do |word| char_count += word.size + 1 if char_count > width word.prepend("n") unless word.start_with?("nn") char_count = word.size + 1 end char_count = word.size - 1 if word.include?("nn") end.join(' ') << lastchar end end class Deck # Used for calculating score CARD_VALUES = { '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, '10' => 10, 'J' => 10, 'Q' => 10, 'K' => 10, 'A' => 11 }.freeze RANKS = %w(2 3 4 5 6 7 8 9 10 J Q K A).freeze # UTF codes for suit symbols SUITS = ["u2660", "u2661", "u2662", "u2663"].freeze attr_reader :cards def initialize new_deck end def new_deck @cards = RANKS.product(SUITS).shuffle end end # This "Player" class implements functionality that the user/player and dealer have in common. Both the user and # dealer inherit this class; I opted for "Gambler" for the user/player class. class Player include Utilities attr_reader :hand, :score def initialize(deck) @deck = deck end def new_hand @hand = [] 2.times { hit } calc_score end def calc_score total = 0 hand.each do |card| total += Deck::CARD_VALUES[card[0]] end aces = hand.count { |a| a[0] == 'A' } while total > App::BUST_VALUE && aces > 0 total -= 10 aces -= 1 end @score = total end private def hit @hand.push(@deck.cards.pop) end end class Gambler < Player attr_accessor :cash attr_reader :name def initialize(deck, cash) super(deck) @cash = cash @name = choose_name end def turn(table) loop do play = play_decision if play == 'h' hit calc_score table.show end break if score > App::BUST_VALUE || play == 's' end end private def choose_name first_time = true the_name = '' cls welcome = "nWelcome to Rodes Black Jack! " loop do say word_wrap("#{first_time ? welcome + 'W' : 'So, w'}hat's your name? ") the_name = gets.chomp break unless the_name.delete(' ').empty? first_time = false say word_wrap("Sorry, only The Man With No Name is allowed to play " "without a name. "), 1 end puts the_name.split.map(&:capitalize).join(' ') end def play_decision loop do say 'Hit or stand (H or S)? ' play = getchar.downcase return play if 'hs'.chars.include?(play) say "nInvalid value. Please try again.", 1 end end end class Dealer < Player attr_accessor :hide_hand def initialize(deck) super @hide_hand = true end def turn(table) @hide_hand = false wait App::SLEEP_TIME loop do table.show wait App::SLEEP_TIME break if score >= App::DEALER_STAND_VALUE hit calc_score end end end # Displays the table. The table shows the header, the player's and dealer's # hands, the scores, and the player's cash. class Table include Utilities def initialize(dealer, gambler) @dealer = dealer @gambler = gambler end def show cls print "#{App::HEADER}" "#{'Dealer'.center(App::SCREEN_WIDTH)}n" "#{create_hand_view(@dealer).center(App::SCREEN_WIDTH)}nn" "#{Deck::SUITS.join(' ').center(App::SCREEN_WIDTH)}nn" "#{create_hand_view(@gambler).center(App::SCREEN_WIDTH)}n" "#{@gambler.name.center(App::SCREEN_WIDTH)}nn" "Player score: #{@gambler.score}n" "Dealer score: #{@dealer.hide_hand ? '??' : @dealer.score}nn" "Player cash: $#{@gambler.cash}nn" end private def create_hand_view(player) return player.hand[0].join if player.class == Dealer && player.hide_hand hand_view = '' player.hand.each_with_index do |card, i| hand_view << card.join hand_view << ' ' unless i + 1 == player.hand.size end hand_view end end # Handles a single round of play, from dealing the hands to figuring the result. class Round include Utilities NATURAL_YES = 'Got one!' NATURAL_NO = 'Not this time.' attr_reader :result def initialize(dealer, gambler, table) @dealer = dealer @gambler = gambler @table = table @result = nil end def play @gambler.new_hand @dealer.new_hand @table.show dealer_check_natural @table.show @result = calc_natural_result return unless @result.nil? @gambler.turn(@table) @result = calc_result return unless @result.nil? @dealer.turn(@table) @result = calc_result end private def calc_natural_result return :natural_push if @dealer.score == App::BUST_VALUE && @gambler.score == App::BUST_VALUE return :dealer_natural if @dealer.score == App::BUST_VALUE return :gambler_natural if @gambler.score == App::BUST_VALUE nil end def calc_result return :gambler_bust if @gambler.score > App::BUST_VALUE return nil if @dealer.hide_hand return :dealer_bust if @dealer.score > App::BUST_VALUE case @gambler.score <=> @dealer.score when 1 :gambler when -1 :dealer else :push end end def dealer_check_natural return unless Deck::CARD_VALUES[@dealer.hand[0][0]] >= 10 print word_wrap("Dealer is checking for a natural #{App::BUST_VALUE} ... ") wait App::SLEEP_TIME print "#{@dealer.score == App::BUST_VALUE ? NATURAL_YES : NATURAL_NO}n" wait App::SLEEP_TIME end end class BlackJack include Utilities BET = 1 NATURAL_MULTIPLIER = 10 APPLY_BET = { gambler_natural: BET * NATURAL_MULTIPLIER, dealer_bust: BET, gambler: BET, natural_push: 0, push: 0, dealer: -BET, gambler_bust: -BET, dealer_natural: -BET }.freeze GAMBLER_STAKE = 50 HAND_RESULT_PROMPT = { gambler_natural: "Player holds a natural #{App::BUST_VALUE}! Player " "wins $#{APPLY_BET[:gambler_natural]}.", dealer_bust: 'Dealer busted. Player wins.', gambler: 'Player wins.', natural_push: "Dealer and player both hold a natural " "#{App::BUST_VALUE}! That's a push.", push: 'Push.', dealer: 'Dealer wins.', gambler_bust: 'Player busted. Dealer wins.', dealer_natural: "Dealer holds a natural #{App::BUST_VALUE}. Dealer wins." }.freeze # Increasing App::BUST_VALUE requires that NEW_DECK_COUNT be increased as # well. To avoid the possibility of an error from trying to deal from an # empty deck, NEW_DECK_COUNT has to be at least as high as the maximum # possible number of cards for both hands. NEW_DECK_COUNT = 18 SPACE = ' ' def initialize @deck = Deck.new @dealer = Dealer.new(@deck) @gambler = Gambler.new(@deck, GAMBLER_STAKE) @table = Table.new(@dealer, @gambler) end def play show_opening_screen loop do round = Round.new(@dealer, @gambler, @table) round.play @dealer.hide_hand = false @gambler.cash += APPLY_BET[round.result] @table.show say HAND_RESULT_PROMPT[round.result], 2 break unless play_again? reset end show_closing_screen end private def closing_screen_message <<~BLOCK Thank you for playing at Rodes Black Jack, #{@gambler.name}!#{SPACE} #{closing_screen_message_which} BLOCK end def closing_screen_message_which case @gambler.cash <=> GAMBLER_STAKE when -1 then closing_screen_message_lose when 1 then closing_screen_message_win else "You have broken even. Oh well, maybe next time.n" end end def closing_screen_message_lose <<~BLOCK You have lost $#{GAMBLER_STAKE - @gambler.cash}.#{SPACE} But not to worry. Just spend a year washing dishes in our kitchen and we'll call it even.#{SPACE} See you tomorrow at 5 a.m. Don't be late!#{SPACE} BLOCK end def closing_screen_message_win <<~BLOCK You have won $#{@gambler.cash - GAMBLER_STAKE}. We'll be keeping the money, of course. After all, it was ours to begin with.#{SPACE} Stop back and see us again!#{SPACE} BLOCK end def opening_screen_message <<~BLOCK Welcome #{@gambler.name}! You have been awarded a gambling spree at Rodes Black Jack, our favorite casino!#{SPACE} Black Jack is the game we play ... sort of. No doubling down, and no splitting pairs.#{SPACE} You have $#{GAMBLER_STAKE} to play with. Bet is $#{BET} per hand.#{SPACE} Black Jack (natural #{App::BUST_VALUE}) pays #{NATURAL_MULTIPLIER} to 1. Good luck!#{SPACE} Please hit #{KEY_ONLY ? 'any key' : '"Enter"'} when you are ready to begin:#{SPACE} BLOCK end def play_again? say word_wrap("Please #{KEY_ONLY ? 'hit' : 'enter'} Q to quit, or " 'anything else to play again: ') char = getchar print "n" return false if !char.nil? && char.casecmp('q').zero? true end def reset @dealer.hide_hand = true return if @deck.cards.count > NEW_DECK_COUNT say 'Reshuffling ... ', 1 wait App::SLEEP_TIME * 2 @deck.new_deck end def show_closing_screen cls print App::HEADER print word_wrap(closing_screen_message) print "Please hit #{KEY_ONLY ? 'any key' : '"Enter"'} to leave ... " getchar print "Bye now.nn" end def show_opening_screen cls print App::HEADER print word_wrap(opening_screen_message) getchar end end game = BlackJack.new game.play |
Python: Blackjack Game
This is a terminal-based version of Blackjack, written in Python.
This is a port of the Ruby blackjack game to Python.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 |
from itertools import product import random import os import time class App: BUST_VALUE = 21 DEALER_STAND_VALUE = 17 SCREEN_WIDTH = 60 HEADER = f"n{'Rodes Black Jack'.center(SCREEN_WIDTH)}n" SLEEP_TIME = 1 # How long to wait between dealer cards dealt, etc. class UtilsMixin: def cls(self): os.system('clear||cls') def say(self, message, new_lines=0): print(message) print("n" * new_lines) def wait(self, interval): time.sleep(interval) def sign(self, val1, val2): difference = val1 - val2 if difference > 0: return 1 elif difference < 0: return -1 else: return 0 class Deck: _CARD_VALUES = { '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '10': 10, 'J': 10, 'Q': 10, 'K': 10, 'A': 11 } _RANKS = '2 3 4 5 6 7 8 9 10 J Q K A'.split() _SUITS = ["u2660", "u2661", "u2662", "u2663"] def __init__(self): self.new_deck() def new_deck(self): cards = [*product(Deck._RANKS, Deck._SUITS)] random.shuffle(cards) self.cards = cards class Player(UtilsMixin): def __init__(self, deck): self.deck = deck self.hand = None def new_hand(self): self.hand = [] for _ in range(2): self.hit() self.calc_score() def calc_score(self): total = 0 for card in self.hand: total += Deck._CARD_VALUES[card[0]] aces = sum(1 for val in self.hand if val[0] == 'A') while total > App.BUST_VALUE and aces > 0: total -= 10 aces -= 1 self.score = total def hit(self): self.hand.append(self.deck.cards.pop()) class Gambler(Player): def __init__(self, deck, cash): super().__init__(deck) self.cash = cash self.name = self.choose_name() def turn(self, table): while True: play = self.play_decision() if play == 'h': self.hit() self.calc_score() table.show() if self.score > App.BUST_VALUE or play == 's': break def choose_name(self): first_time = True the_name = '' self.cls() welcome = "nWelcome to Rodes Black Jack! " while True: the_name = input(f"{welcome + 'W' if first_time else 'So, w'}hat's your name? ") if the_name.replace(' ', '') == '': first_time = False self.say("nSorry, only The Man With No Name is allowed to play without a name.") else: break return the_name def play_decision(self): while True: play = input('Hit or stand (H or S)? ').lower() if play in 'hs': return play else: self.say("nInvalid value. Please try again.", 1) class Dealer(Player): def __init__(self, deck): super().__init__(deck) self.hide_hand = True def turn(self, table): self.hide_hand = False self.wait(App.SLEEP_TIME) while True: table.show() self.wait(App.SLEEP_TIME) if self.score >= App.DEALER_STAND_VALUE: break else: self.hit() self.calc_score() class Table(UtilsMixin): def __init__(self, dealer, gambler): self.dealer = dealer self.gambler = gambler def show(self): self.cls() print(f""" {App.HEADER} {'Dealer'.center(App.SCREEN_WIDTH)} {self.create_hand_view(self.dealer).center(App.SCREEN_WIDTH)} {' '.join(Deck._SUITS).center(App.SCREEN_WIDTH)} {self.create_hand_view(self.gambler).center(App.SCREEN_WIDTH)} {self.gambler.name.center(App.SCREEN_WIDTH)} Player score: {self.gambler.score} Dealer score: {'??' if self.dealer.hide_hand else self.dealer.score} Player cash: ${self.gambler.cash} """) def create_hand_view(self, player): if type(player).__name__ == 'Dealer' and player.hide_hand: return ''.join(player.hand[0]) return ' '.join(''.join(h) for h in player.hand) class Round(UtilsMixin): NATURAL_YES = 'Got one!' NATURAL_NO = 'Not this time.' def __init__(self, dealer, gambler, table): self.dealer = dealer self.gambler = gambler self.table = table self.result = None def play(self): self.gambler.new_hand() self.dealer.new_hand() self.table.show() self.dealer_check_natural() self.result = self.calc_natural_result() if self.result != None: self.dealer.hide_hand = False self.table.show() return self.table.show() self.gambler.turn(self.table) self.result = self.calc_result() if self.result != None: return self.dealer.turn(self.table) self.result = self.calc_result() def calc_natural_result(self): if (self. dealer.score == App.BUST_VALUE and self.gambler.score == App.BUST_VALUE): return 'natural_push' if self. dealer.score == App.BUST_VALUE: return 'dealer_natural' if self. gambler.score == App.BUST_VALUE: return 'gambler_natural' return None def calc_result(self): if self.gambler.score > App.BUST_VALUE: return 'gambler_bust' if self.dealer.hide_hand: return None if self.dealer.score > App.BUST_VALUE: return 'dealer_bust' return ['push', 'gambler', 'dealer'][self.sign(self.gambler.score, self.dealer.score)] def dealer_check_natural(self): if Deck._CARD_VALUES[self.dealer.hand[0][0]] < 10: return print(f"Dealer is checking for a natural {App.BUST_VALUE} ... ") self.wait(App.SLEEP_TIME) print(f"{self.NATURAL_YES if self.dealer.score == App.BUST_VALUE else self.NATURAL_NO}n") self.wait(App.SLEEP_TIME) class BlackJack(UtilsMixin): BET = 1 NATURAL_MULTIPLIER = 10 GAMBLER_STAKE = 50 SPACE = ' ' # Increasing App.BUST_VALUE requires that NEW_DECK_COUNT be increased as # well. To avoid the possibility of an error from trying to deal from an # empty deck, NEW_DECK_COUNT has to be at least as high as the maximum # possible number of cards for both hands. NEW_DECK_COUNT = 18 APPLY_BET = { 'gambler_natural': BET * NATURAL_MULTIPLIER, 'dealer_bust': BET, 'gambler': BET, 'natural_push': 0, 'push': 0, 'dealer': -BET, 'gambler_bust': -BET, 'dealer_natural': -BET } HAND_RESULT_PROMPT = { 'gambler_natural': f"Player holds a natural {App.BUST_VALUE}! Player wins ${APPLY_BET['gambler_natural']}.", 'dealer_bust': 'Dealer busted. Player wins.', 'gambler': 'Player wins.', 'natural_push': f"Dealer and player both hold a natural {App.BUST_VALUE}! That's a push.", 'push': 'Push.', 'dealer': 'Dealer wins.', 'gambler_bust': 'Player busted. Dealer wins.', 'dealer_natural': f"Dealer holds a natural {App.BUST_VALUE}. Dealer wins." } def __init__(self): self.deck = Deck() self.dealer = Dealer(self.deck) self.gambler = Gambler(self.deck, self.GAMBLER_STAKE) self.table = Table(self.dealer, self.gambler) def play(self): self.show_opening_screen() while True: round = Round(self.dealer, self.gambler, self.table) round.play() self.dealer.hide_hand = False self.gambler.cash += self.APPLY_BET[round.result] self.table.show() self.say(self.HAND_RESULT_PROMPT[round.result], 2) if not self.play_again(): break self.reset() self.show_closing_screen() def closing_screen_message(self): return f""" Thank you for playing at Rodes Black Jack, {self.gambler.name}!{self.SPACE} {self.closing_screen_message_which()} """ def closing_screen_message_which(self): return [ "You have broken even. Oh well, maybe next time.n", self.closing_screen_message_win(), self.closing_screen_message_lose() ][self.sign(self.gambler.cash, self.GAMBLER_STAKE)] def closing_screen_message_lose(self): return f""" You have lost ${self.GAMBLER_STAKE - self.gambler.cash}.{self.SPACE} But not to worry. Just spend a year washing dishes in our kitchen and we'll call it even.{self.SPACE} See you tomorrow at 5 a.m. Don't be late!{self.SPACE} """ def closing_screen_message_win(self): return f""" You have won ${self.gambler.cash - self.GAMBLER_STAKE}. We'll be keeping the money, of course. After all, it was ours to begin with.{self.SPACE} Stop back and see us again!{self.SPACE} """ def opening_screen_message(self): return f""" Welcome {self.gambler.name}! You have been awarded a gambling spree at Rodes Black Jack, our favorite casino!{self.SPACE}Black Jack is the game we play ... sort of. No doubling down, and no splitting pairs.{self.SPACE} You have ${self.GAMBLER_STAKE} to play with. Bet is ${self.BET} per hand.{self.SPACE}Black Jack (natural {App.BUST_VALUE}) pays {self.NATURAL_MULTIPLIER} to 1. Good luck!{self.SPACE} """ def play_again(self): char = input('Please enter Q to quit, or anything else to play again: ') print("n") return char.lower() != 'q' def reset(self): self.dealer.hide_hand = True if len(self.deck.cards) > self.NEW_DECK_COUNT: return self.say('Reshuffling ... ', 1) self.wait(App.SLEEP_TIME * 2) self.deck.new_deck() def show_closing_screen(self): self.cls() print(App.HEADER) print(self.closing_screen_message()) input(f"Please hit "Enter" to leave ... ") print("Goodbye!nn") def show_opening_screen(self): self.cls() print(App.HEADER) print(self.opening_screen_message()) input(f"Please hit "Enter" to continue ... ") game = BlackJack() game.play() |
Web Technologies: HTML5, CSS, jQuery, PHP, MySQL
This code sample from the PPLS Tracking System application displays a modal dialog box. It handles the adding of parts to or removing of parts from a particular distributor in a vendor table. The context is editing a specific vendor record. The user may add or remove parts carried by the vendor. If adding, the user gets a list of all parts not currently carried by the vendor, checks the ones to add and saves. If removing, the user gets a list of all parts currently carried by the vendor, checks the ones to remove and saves.
The code also supports toggling of detail data for each record. The user clicks a button to the left of the row, and the detail is presented in a dropdown format underneath the row. Clicking again removes the detail.
This code sample from the PPLS Tracking System application displays a modal dialog box. It handles the adding of parts to or removing of parts from a particular distributor in a vendor table. The context is editing a specific vendor record. The user may add or remove parts carried by the vendor. If adding, the user gets a list of all parts not currently carried by the vendor, checks the ones to add and saves. If removing, the user gets a list of all parts currently carried by the vendor, checks the ones to remove and saves.
The code also supports toggling of detail data for each record. The user clicks a button to the left of the row, and the detail is presented in a dropdown format underneath the row. Clicking again removes the detail.
This is one of several modal dialogs of this type. Some of the code given here is reused for other editing contexts involving the adding or removing of child records.
The sample contains all the modules required for this particular dialog box, using a range of popular web technologies: JavaScript, jQuery, jQuery-UI, CSS, AJAX, JSON, PHP and MySQL. It also uses the jQuery DataTables plugin.
vdrmodal.php, the modal dialog page
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
<?php session_start(); require_once('classes/databaseconnector.php'); require_once('includes/phpConstants.php'); require_once('includes/phpHelpers.php'); require_once('includes/phpFormatHelpers.php'); $theDb = new CDbConnector(); $theDb->Connect(); $Id = $_GET['id']; $AOrR = $_GET['aorr']; // Add or Remove records; passed in via GET variables $rs = GetChildren('getPartDists' , $Id, $AOrR); ?> <!DOCTYPE html> <html> <head> <script type="text/javascript" language="JavaScript"> ///This function creates the HTML for a detail table and returns it as a string. function format(d) { var x = '<table cellpadding="5" cellspacing="0" border="0" style="padding-left:50px;">'+ '<tr>'+ '<td>CAGE:</td>'+ '<td>'+d.oCAGE+'</td>'+ '</tr>'+ '<tr>'+ '<td>Status:</td>'+ '<td>'+d.oStatus+'</td>'+ '</tr>'+ '<tr>'+ '<td>UOM:</td>'+ '<td>'+d.UOM+'</td>'+ '</tr>'+ '<tr>'+ '<td>QUP:</td>'+ '<td>'+d.QUP+'</td>'+ '</tr>'+ '</table>'; return x; } $(document).ready( function() { var theData = { "data": <?php echo $rs; ?> } for(var i=0;i<theData.data.length;i++) { theData.data[i].checkBox = '<input type="checkbox">'; } var table = $('#vdrDetail').DataTable( { 'data': theData.data, 'columns': [ { 'className': 'details-control', // The details-control class has the background value set to a button image 'orderable': false, 'data': null, 'defaultContent': '', 'width': '10px' }, { 'data': 'PartNo' }, { 'data': 'pStatus' }, { 'data': 'NSN' }, { 'data': 'Desc' }, { 'data': 'oName' }, { 'className': 'row-select', 'orderable': false, 'data': 'checkBox', 'width': '7px', } ], 'order': [[1, 'asc']], paginate: false, scrollY: '352px', scrollCollapse: true, 'infoCallback': function( settings, start, end, max, total, pre ) { if (0 == max) { return 'Showing 0 records'; } if (1 == max) { return 'Showing 1 record'; } if (total == max) { return 'Showing ' + max + ' records'; } return 'Showing ' + total + ' of ' + max + ' records'; }, }); /// Sets up the button click event for child rows. It's important to use on() here instead of click(), since the button is set /// up dynamically while rendering the data table. click() will register the event only for instances of the class that exist on /// page load, so if you re-render the table, the event won't be registered for new instances of the class. on() delegates the /// event to the class itself rather than registering existing instances of it as listeners. That way, any class members get /// registered whenever they are instantiated during the lifetime of the page. $('#vdrDetail tbody').on('click', 'td.details-control', function () { var tr = $(this).closest('tr'); var row = table.row( tr ); if ( row.child.isShown() ) { row.child.hide(); tr.removeClass('shown'); // shows a green button with a plus on it for "open" } else { row.child( format(row.data()) ).show(); tr.addClass('shown'); // shows a red button with a minus on it for "close" } }).on('click', 'td.row-select input', function(e) { e.stopPropagation(); // Clicking on the check box automatically toggles the check; the click event then propagates to the // container, whose click event toggles the check back again--unless we stop it here. $('#btnSave').button('option', 'disabled', 0 == $('#vdrDetail input:checked').length); // Disables the save button if no // checkboxes are checked }).on('click', 'td.row-select', function() { // So the user can click anywhere in the cell rather than just the checkbox this.firstChild.checked = !this.firstChild.checked; $('#btnSave').button('option', 'disabled', 0 == $('#vdrDetail input:checked').length); }); return table; }); </script> </head> <body> <table id="vdrDetail" class="display" cellspacing="0" width="100%"> <thead> <tr> <th></th> <th>Part No</th> <th>Status</th> <th>NSN</th> <th>Desc</th> <th>OEM</th> <th></th> </tr> </thead> </table> <?php $theDb->Disconnect(); unset ($theDb); ?> </body> </html> |
dialog()
function code, from viewvendor.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
$('#btnPartAdd, #btnPartRem').on('click', function(e, ui) { var aOrR = ('btnPartAdd' == this.id ? 1 : 0); // If the user hit Add, we're adding, if he hit Removed, we're removing $('#dialogChildren').load('vdrmodal.php?id=' + id + '&aorr=' + aOrR, function(table) { $('#dialogChildren').dialog( { modal: true, maxHeight: 580, width: 1100, position: {my:'top', at:'top+60'}, buttons: [ { id: 'btnSave', text: 'Save', click: function(e, ui) { var theData = $('#vdrDetail').DataTable().rows().data(); var theNodes = $('#vdrDetail').DataTable().rows().nodes(); var paramString = ''; for(var i=0;i<theData.length;i++) { // find every checked row and add or remove its data from the appropriate // underlying table if (theNodes[i].cells[6].children[0].checked) { paramString = 'table=partsdists&field1=PartId&field2=DistId&aorr=' + aOrR + '&id1=' + theData[i].Id + '&id2=' + id; $.ajax( { url: 'cdchildren.php', // handles the adding or removing of child records data: paramString }); } } $('#hdnId').val(id); $('#hdnAccordionActive').val(2); $('#frmPostVars').submit(); $(this).dialog('close'); }, disabled: true }, { text: 'Cancel', click: function() { $(this).dialog('close'); } } ], title: (1 == aOrR ? 'Adding' : 'Removing') + ' Parts', open: function(e, ui) { $('#vdrDetail').DataTable().columns.adjust(); $('.ui-dialog-buttonset button:first-of-type .ui-button-text') .text((1 == aOrR ? 'Add' : 'Remove') + ' Selected'); } }); }); }); |
cdchildren.php: adds and removes child records
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<?php session_start(); require_once('classes/databaseconnector.php'); require_once('includes/phpHelpers.php'); $theDb = new CDbConnector(); $theDb->Connect(); $Table = $_GET['table']; $AOrR = $_GET['aorr']; $Id1 = $_GET['id1']; $Id2 = $_GET['id2']; $Field1 = $_GET['field1']; $Field2 = $_GET['field2']; if (0 == $AOrR) { RemChild($Table, $Field1, $Field2, $Id1, $Id2); } else { AddChild($Table, $Field1, $Field2, $Id1, $Id2); } $theDb->Disconnect(); unset ($theDb); ?> |
AddChild, RemChild, GetChildren and rsToJSON, from phpHelpers.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
/// AddChild and RemChild are used to add and remove records from an associative entity table which maintains a many-to-many relationship /// between two kernel entity tables. The structure of the table is always two fields, which are a composite primary key. The record /// contents are always one foreign key from each of the kernel entity tables. $Table is the associative entity table name, $Field1 and /// $Field2 are the names of the fields making up the composite primary key in the table, and $Id1 and $Id2 are the values for the /// record to be created or deleted. function AddChild($Table, $Field1, $Field2, $Id1, $Id2, $theDb) { $query = "INSERT INTO $Table ($Field1, $Field2) VALUES ('$Id1', '$Id2')"; $rs = mysqli_query($theDb->connection, $query) or die(mysqli_error($theDb->connection)); } function RemChild($Table, $Field1, $Field2, $Id1, $Id2, $theDb) { $query = "DELETE FROM $Table WHERE $Field1 = '$Id1' AND $Field2 = '$Id2'"; $rs = mysqli_query($theDb->connection, $query) or die(mysqli_error($theDb->connection)); } /// GetChildren is used to call several different stored procedures. Each of them pulls all the child records from a specific child table /// for a specific record in a parent table, or if $Param2 is set, pulls all the child records from said child table that are NOT associated /// with said parent record. $Param2 is set when in the Add context, and is not set when in the Remove context. The results are returned as /// JSON data to the calling web page, to be displayed by the DataTable functions. function GetChildren($ProcName, $Param1, $Param2, $theDb) { $query = "CALL " . $ProcName . "(" . $Param1 . (isset($Param2) ? ", " . $Param2 : "") . ")"; $rs = mysqli_query($theDb->connection, $query) or die(mysqli_error($theDb->connection)); return rsToJSON($rs); } /// rsToJSON casts a php resultset to JSON for consumption by AJAX calls. function rsToJSON($rs) { return json_encode(rsToArray($rs)); } |
cdbconnector.php: defines the CDbConnector class, used to connect to the MySQL database
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
<?php class CDbConnector { var $hostname; // The database server to which we are connecting var $usernamedb; // The user name that we use to connect to the DB var $passworddb; // The password that we use to connect to the DB var $dbName; // The name of the database. var $connection; // Connection to DB server var $dbSelect; // Reference to the database that we will use var $bConnected; // Constructor function __construct() { $this->bConnected = false; } function Connect() { $this->hostname = '************'; $this->usernamedb = '************'; $this->passworddb = '************'; $this->dbName = "************"; $this->connection = mysqli_connect( $this->hostname, $this->usernamedb, $this->passworddb) or die('Cannot establish connection'); $this->dbSelect = mysqli_select_db($this->connection, $this->dbName) or die('Cannot select DB'); $this->bConnected = true; } function Disconnect() { if (true == $this->bConnected) mysqli_close($this->connection); } } ?> |
getPartDists: MySQL proc to get parts for one distributor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
CREATE PROCEDURE getPartDists (theID int, adding bit) BEGIN IF adding = 1 then -- Get all the parts that aren't associated with the vendor select p.id as Id, n.nsn as NSN, n.ShortDesc as 'Desc', p.partno as PartNo, p.memo as Memo, v.CAGE as oCAGE, vs.Code as oStatus, v.Name as oName, ps.Code as pStatus, u.Code as UOM, q.Description as QUP from parts p join nsns n on p.nsnid = n.id left join vendors v on p.vendorid = v.id left join vendorstatus vs on v.statusid = vs.id join uoms u on p.uomid = u.id join qups q on p.qupid = q.id join partstatus ps on p.statusid = ps.id where not exists (select partid from partsdists where partid = p.id and distid = theID); ELSE -- Get all the parts that are select p.id as Id, n.nsn as NSN, n.ShortDesc as 'Desc', p.partno as PartNo, p.memo as Memo, v.CAGE as oCAGE, vs.Code as oStatus, v.Name as oName, ps.Code as pStatus, u.Code as UOM, q.Description as QUP from parts p join nsns n on p.nsnid = n.id left join vendors v on p.vendorid = v.id left join vendorstatus vs on v.statusid = vs.id join uoms u on p.uomid = u.id join qups q on p.qupid = q.id join partstatus ps on p.statusid = ps.id where exists (select partid from partsdists where partid = p.id and distid = theID); END IF; END$$ |
C#: Multithreading, Reflection and Dynamic Binding, Using a RESTful Service
This sample code demonstrates use of various features and techniques in C#. The code implements these technical requirements:
Access data exposed as a web service, and export it to a local file.
Both XML and JSON formats need to be supported by requirement 1.
The export functionality must be a singleton instance, and must support multithreading.
The thread, data access, and export functionality should be addressed as separate concerns.
Provide a custom event that is raised when the export is finished, with two arguments: one to keep track of whether the export succeeded and one to provide a target file name for the exported data.
Define all of the export class’s functionality in abstract terms, and have the class implement the abstractions.
This sample code demonstrates use of various features and techniques in C#. The code implements these technical requirements:
Access data exposed as a web service, and export it to a local file.
Both XML and JSON formats need to be supported by requirement 1.
The export functionality must be a singleton instance, and must support multithreading.
The thread, data access, and export functionality should be loosely coupled.
Provide a custom event that is raised when the export is finished, with two arguments: one to keep track of whether the export succeeded and one to provide a target file name for the exported data.
Define all of the export class’s functionality in abstract terms, and have the class implement the abstractions.
To accomplish this, we have three separate classes, one to retrieve data, one to export it, and a multithreaded client:
The
NorthwindDataRetrieve
class uses Microsoft’s (abbreviated) Northwind database, which they expose as anOData
service on Azure. The method has an overloadedGetData
method that returns various genericList
objects containing data.The
DataExport
class exposes a thread-safe singleton instance of itself, with a genericExport
method that can accept any of theList
objects returned by theNorthwindDataRetrieve.GetData
method. This method also accepts an argument for whether the exported data are to be in XML or in JSON format. It also raises a custom event when finished.This event can’t quite be defined in fully abstract terms (i. e. in terms of interfaces), per requirement 6. Custom arguments for an event have to inherit the existing
EventArgs
class, andEventArgs
doesn’t expose a public interface (e.g. “iEventArgs”). Therefore, it isn’t possible to define a class deriving from it as an interface (interfaces can’t inherit concrete classes; they have to inherit interfaces). The closest we can come to a fully abstract definition is to define an abstract class that inheritsEventArgs
and defines the custom event arguments as abstract, while defining everything else in terms of interfaces and delegates.(Microsoft’s position on why they don’t allow custom implementations of the
EventArgs
class itself seems reasonable: first, the process of passing arguments to event procedures is a specialization of the process of passing arguments to procedures in general, and procedure logic is already tightly coupled with the logic of passing arguments to them. Therefore, developers using the CLR are unlikely to derive significant advantage from being allowed to create custom implementations of theEventArgs
class. Second, if Microsoft decide to change the implementation ofEventArgs
, they would run the risk of breaking custom implementations of the class if custom implementations were allowed. They don’t feel that the limited additional functionality justifies this risk.)The
Program
class is the client. TheMain
method instantiates some threads that call theDoWork
method, passing arguments in a class calledThreadParams
. This class has a target file name, a file type (JSON or XML), and an instance of a List object that will hold the retrieved data.DoWork
callsGetData
, passes the resulting List to theExport
method, and calls the event handler onceExport
raises theExportFinished
event.
Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Reflection; using DataExport; using NorthwindDataRetrieve; using System.IO; namespace ConsoleClient // The client for the DataExporter class. { public enum FileType { JSON, XML } /// <summary> /// This class represents the parameters passed to each thread in an object. /// </summary> class ThreadParams { private string _filePath; private FileType _fileType; private IEnumerable<object> _listType; public ThreadParams(string filePath, FileType fileType, IEnumerable<object> listType) { _filePath = filePath; _fileType = fileType; _listType = listType; } public string filePath { get { return _filePath; } } public FileType fileType { get { return _fileType; } } public IEnumerable<object> listType { get { return _listType; } } } class Program { /// <summary> /// This method sets up three threads, which will each use the same DataExporter object. The threads' Join method joins them to the /// current thread, the one upon which this method (Main) is executing. If we didn't do this, they would execute asynchronously /// relative to this thread, the result of which would be that the WriteLine and ReadKey methods would execute before all the /// threads are done with their work. This of course would put all the messages out of order. /// </summary> /// <param name="args"></param> static void Main(string[] args) { var p = new ThreadParams[] // An array of parameters corresponding to the array of threads { new ThreadParams(@"c:cs_sampledataCategoriesJSON.txt", FileType.JSON , new List<Categories>()), new ThreadParams(@"c:cs_sampledataProdSuppliersXML.txt", FileType.XML, new List<ProductReorders>()), new ThreadParams(@"c:cs_sampledataSupplierProdsXML.txt", FileType.XML, new List<SupplierProducts>()) }; var t = new Thread[3]; for (int i=0; i<3; i++) { t[i] = new Thread(new ParameterizedThreadStart(doWork)); t[i].Name = "Thread " + i.ToString(); t[i].Start(p[i]); t[i].Join(); }; Console.WriteLine("strike a key when ready..."); // The threads all write messages prior to this line being executed Console.ReadKey(true); } /// <summary> /// This method does the work for each thread calling it, calling NorthwindDataRetriever.GetData to get a series of records, and /// using the Singleton DataExporter object to write them to a file. See notes to the Instance method in the DataExport class for /// thread safety considerations. /// </summary> /// <param name="tPar"></param> static void doWork(object prms) { try { if (!(prms is ThreadParams)) { throw new ArgumentException(Thread.CurrentThread.Name + ": Argument passed on thread start is not of type ThreadParams; thread aborted"); } var p = (ThreadParams) prms; // So we don't have to recast prms every time we want to use it DataExporter de = DataExporter.Instance; // Simplest way to implement a singleton var cRet = new NorthwindDataRetriever(); // This combination of methods uses reflection to dynamically invoke the NorthwindDataRetreiver.GetData method, with these // steps: // 1. Use GetType to get the method list for the NorthwindDataRetriever object. // 2. Use GetMethod to get the method signature for GetData. GetMethod (the version we're using here; there are several // overloads) takes two arguments: the name of the method as a string, and an array of Type objects containing the // method's parameters. // 3. Populate the Type array with the desired parameter values. In this case, GetData has only one parameter: cList. // 4. There are three possible types that will fit the cList parameter (GetData is overloaded). We need to find out which // type was passed into doWork via the ThreadParams object. Use GetType again to do this. // 5. Since cList is an out parameter, use MakeByRefType to declare it as such. MethodBase cMethod = cRet.GetType().GetMethod("GetData", new Type[] { p.listType.GetType().MakeByRefType() } ); // The MethodBase object's Invoke method takes two arguments: a reference to the object whose method is being invoked, and // an array of System.Object objects. Invoke uses the latter to pass the parameters from the list of Type objects set up by // the GetMethod method. Any out parameters in the invoked method are therefore returned as members of this array, and are // therefore cast to System.Object. We therefore need to recast this System.Object back to the type of the Type object that // we passed to the GetMethod method in the line above. // // The problem is that we can't do that explicitly in code because we don't know exactly which type we're working with until // runtime, and the compiler won't allow an implicit cast from System.Object to any other type. (The compiler won't allow // an implicit narrowing conversion, i.e. a conversion from a wider to a narrower type such as from Int64 to Int32. While it // might seem less than obvious that System.Object is "wider" than any of the object types that derive from it, the salient // consideration is that instances of derived classes qualify as instances of their base class, while the reverse is not // so.) To get around this, we need to set the cArgs variable to dynamic. This bypasses the compiler's static type checking, // and allows cArgs, when the Export method gets called, to be implicitly cast as whichever List type got passed in here // from the ThreadParam object. dynamic cArgs = new object[1]; cMethod.Invoke(cRet, cArgs); de.OnExportFinished += new DataExporterEvent(de_OnExportFinished); // Subscribes to the OnExportFinished event de.Export(cArgs[0], p.filePath, p.fileType); de.OnExportFinished -= new DataExporterEvent(de_OnExportFinished); // Unsubscribes from same } catch (NullReferenceException ex) { Console.WriteLine("Null Reference Exception in " + Thread.CurrentThread.Name + ": " + ex.Message + " (probable cause is incorrect object type in listType thread parameter)"); return; } catch (ArgumentException ex) { Console.WriteLine(ex.Message); } catch (Exception ex) { Console.WriteLine("Unhandled Exception: " + ex.Message); } } /// <summary> /// Event handler for DataExport.OnExportFinished event. /// </summary> static void de_OnExportFinished(iDataExporter sender, aDEEventArgs deargs) { switch (deargs.ExportStatus) { case 0: Console.WriteLine("Export routine for " + Thread.CurrentThread.Name + " failed with this error: " + deargs.ReturnMessage); break; default: Console.WriteLine("Export to " + deargs.FileName + " successful using " + Thread.CurrentThread.Name); break; } } } } |
NorthWindDataRetrieve.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
using System; using System.Collections.Generic; using System.Linq; // (To use the OData Northwind service, a service reference to "http://services.odata.org/Northwind/Northwind.svc/" must be included in // the project.) using NorthwindDataExporter.ODataNorthwindService; namespace NorthwindDataRetrieve { /// <summary> /// This is one of the classes that can be plugged into the List object the overloaded NorthwindDataRetriever.GetData() method returns /// </summary> public class Categories { public int CategoryID; public string CategoryName; public string Description; } /// <summary> /// This is one of the classes that can be plugged into the List object the overloaded NorthwindDataRetriever.GetData() method returns /// </summary> public class ProductReorders { public int ProductID; public string ProductName; public decimal? UnitPrice; public string QuantityPerUnit; public int? UnitsInStock; public int? UnitsOnOrder; public int? ReorderLevel; public int SupplierID; public string SupplierName; public string ContactName; public string ContactPosition; public string ContactPhone; } /// <summary> /// This is one of the classes that can be plugged into the List object the overloaded NorthwindDataRetriever.GetData() method returns /// </summary> public class SupplierProducts { public int? SupplierID; public string SupplierName; public List<SupplierProduct> Products; } /// <summary> /// This class is plugged into the Products List in the SupplierProducts class /// </summary> public class SupplierProduct { public int ProductID; public string ProductName; public decimal? UnitPrice; public string QuantityPerUnit; public int? UnitsInStock; public int? UnitsOnOrder; public int? ReorderLevel; } /// <summary> /// This class accesses Microsoft's Northwind database through a RESTful service exposed by odata.org. ("NorthwindEntities" is the /// top-level object exposed by Northwind.svc.) The GetData method has three overloads, each taking a different type of List object /// (defined above) as an out parameter. In each case, the method uses Linq to pull data into the List object. /// </summary> public class NorthwindDataRetriever { NorthwindEntities dc = new NorthwindEntities (new Uri("http://services.odata.org/Northwind/Northwind.svc")); /// <summary> /// This method gets a list of categories (using Linq method syntax). /// </summary> /// <returns></returns> public void GetData(out List<Categories> cList) { cList = dc.Categories.Select(c => new Categories { CategoryID = c.CategoryID, CategoryName = c.CategoryName, Description = c.Description } ).ToList(); } /// <summary> /// This method gets a list of products that need to be reordered (i. e. products whose reorder level exceeds the sum of units in /// stock and units on order). The result set includes contact info for the supplier, and is sorted by supplier. /// </summary> public void GetData(out List<ProductReorders> cList) { cList = ( from p in dc.Products where p.UnitsOnOrder + p.UnitsInStock < p.ReorderLevel orderby p.Supplier.CompanyName, p.ProductName select new ProductReorders { SupplierName = p.Supplier.CompanyName, ContactName = p.Supplier.ContactName, ContactPosition = p.Supplier.ContactTitle, ContactPhone = p.Supplier.Phone, ProductID = p.ProductID, ProductName = p.ProductName, UnitPrice = p.UnitPrice, QuantityPerUnit = p.QuantityPerUnit, UnitsInStock = p.UnitsInStock, UnitsOnOrder = p.UnitsOnOrder, ReorderLevel = p.ReorderLevel } ).ToList(); } /// <summary> /// This method gets a list of suppliers and the products they sell. It preserves nesting for the products in the result set, i.e. /// each Supplier instance has a Products property which is a list of instances of all the products that supplier carries. (The /// alternative is to "flatten" each Supplier instance, as in the GetProductReorders method above.) /// </summary> /// <returns></returns> public void GetData(out List<SupplierProducts> cList) { cList = ( from s in dc.Suppliers select new SupplierProducts { SupplierID = s.SupplierID, SupplierName = s.CompanyName, Products = ( from p in s.Products select new SupplierProduct { ProductID = p.ProductID, ProductName = p.ProductName, UnitPrice = p.UnitPrice, QuantityPerUnit = p.QuantityPerUnit, UnitsInStock = p.UnitsInStock, UnitsOnOrder = p.UnitsOnOrder, ReorderLevel = p.ReorderLevel } ).ToList() } ).ToList(); } } } |
DataExport.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
using System; using System.Collections.Generic; using System.Text; using System.Data; using System.Xml.Serialization; using System.IO; using System.Web.Script.Serialization; using NorthwindDataRetrieve; using ConsoleClient; namespace DataExport { /// <summary> /// This abstract class derives from EventArgs, and also defines two additional custom event arguments that will be passed to a /// DataExporterEvent delegate. /// </summary> public abstract class aDEEventArgs:EventArgs { abstract public int ExportStatus { get; } abstract public string ReturnMessage { get; } abstract public string FileName { get; } } /// <summary> /// This is the delegate that defines the signature of the DataExporterEvent Event. The definition implicitly stipulates that any class /// using it must implement the iDataExporter interface, and also that such a class must implement the aDEEventArgs abstract class. /// </summary> public delegate void DataExporterEvent(iDataExporter sender, aDEEventArgs deargs); /// <summary> /// The main interface. This interface defines an event of type DataExporterEvent, which in turn specifies an aDEEventArgs type for one /// of its arguments. /// </summary> public interface iDataExporter { void Export<T>(List<T> exportList, string filePath, FileType fileType) where T: class; event DataExporterEvent OnExportFinished; } /// <summary> /// This class implements aDEEventArgs, thus providing the ExportStatus and FileName arguments to an event /// handler. Properties are read only, being passed as initial values in the constructor. /// </summary> public class DEEventArgs: aDEEventArgs { private int es; private string rmsg; private string fname; //Overriding the default constructor to require two arguments as stipulated in the aDEEventArgs abstract class. public DEEventArgs (int exportStatus, string returnMessage, string fileName) { es = exportStatus; rmsg = returnMessage; fname = fileName; } //Implementing the two arguments defined in the aDEEventArgs abstract class. public override int ExportStatus { get { return es; } } public override string ReturnMessage { get { return rmsg; } } public override string FileName { get { return fname; } } } /// <summary> /// This class is implemented as a singleton. Although technically it isn't necessary here to make the class sealed for it to be /// a singleton (we're not going to derive any classes from it anyway), derived classes could be instantiated and thereby break /// the singleton pattern. Therefore best practice is to make the class sealed. /// </summary> public sealed class DataExporter : iDataExporter { private static readonly DataExporter instance = new DataExporter(); //Holds a private reference to the singleton instance. public event DataExporterEvent OnExportFinished; /// <summary> /// Overriding the default constructor to make it private. Doing this has the effect of requiring instantiation via a method /// (as opposed to using the new keyword), in this case the Instance property accessor method. /// </summary> private DataExporter() { } /// <summary> /// Making the Instance property static has the de facto effect of implementing the singleton pattern, since there is only one /// instance of the object. Thread safety is guaranteed by the CLR. This is simpler than the more specific "roll your own" /// version of singleton pattern implementation, which requires locks to be thread safe. /// </summary> public static DataExporter Instance { get { return instance; } } /// <summary> /// The Export method is a generic method, allowing it to accept List objects of any type as a parameter. The "class" /// constraint restricts lists to lists of objects (as opposed to strings or structs, for example). /// </summary> public void Export<T>(List<T> exportList, string filePath, FileType fileType) where T: class { try { switch (fileType) { case FileType.JSON: var json = new JavaScriptSerializer().Serialize(exportList); File.WriteAllText(filePath, json); break; case FileType.XML: XmlSerializer serializer = new XmlSerializer(exportList.GetType()); using (TextWriter writer = new StreamWriter(filePath)) { serializer.Serialize(writer, exportList); } break; default: throw new Exception("Error that *should* never occur; since we're using an enum, any code that could hit it won't compile"); } DEEventArgs deargs = new DEEventArgs(1, "", filePath); if (null != OnExportFinished) { OnExportFinished(this, deargs); } } catch (IOException ex) { DEEventArgs deargs = new DEEventArgs(0, ex.Message, filePath); if (null != OnExportFinished) { OnExportFinished(this, deargs); } } catch (Exception ex) { DEEventArgs deargs = new DEEventArgs(0, "Unhandled Error: " + ex.Message, filePath); if (null != OnExportFinished) { OnExportFinished(this, deargs); } } } } } |
VB.Net: Multithreading, Using a RESTful Service
This code sample is the functional equivalent, written in VB, of the code in the preceding C# sample. The comments here are limited to those which do not apply to both samples. For full comments, please refer to the C# sample.
This code sample is the functional equivalent, written in VB, of the code in the C# sample. The comments here are limited to those which do not apply to both samples. For full comments, please refer to the C# sample.
Program.vb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
' Option Strict On results in a compiler error in the doWork method. See the notes there for further explanation. Option Explicit On Option Infer On Imports System.Threading Imports System.Reflection Imports NorthwindDataExporter.DataExport Imports NorthwindDataExporter.NorthwindDataRetrieve Public Enum FileType JSON XML End Enum Module Program Class ThreadParams Private _filePath As String Private _fileType As Byte Private _listType As IEnumerable(Of Object) Public Sub New(filePath As String, fileType As FileType, listType As IEnumerable(Of Object)) _filePath = filePath _fileType = fileType _listType = listType End Sub Public ReadOnly Property filePath As String Get Return _filePath End Get End Property Public ReadOnly Property fileType As Byte Get Return _fileType End Get End Property Public ReadOnly Property listType As IEnumerable(Of Object) Get Return _listType End Get End Property End Class Sub Main() Dim p As ThreadParams() = { New ThreadParams("c:Temp2CategoriesJSON.txt", FileType.JSON, New List(Of Categories)), New ThreadParams("c:Temp2SuppliersXML.txt", FileType.XML, New List(Of ProductReorders)), New ThreadParams("c:Temp2SuppliersJSON.txt", FileType.JSON, New List(Of SupplierProducts)) } Dim t(2) As Thread For i As Integer = 0 To 2 t(i) = New Thread(New ParameterizedThreadStart(AddressOf DoWork)) With t(i) .Name = "Thread " & i.ToString .Start(p(i)) .Join() End With Next Console.WriteLine("strike a key when ready...") Console.ReadKey(True) End Sub Private Sub DoWork(parms As Object) Try If Not TypeOf parms Is ThreadParams Then Throw New ArgumentException(Thread.CurrentThread.Name + ": Argument passed on thread start is not of type ThreadParams; thread aborted") End If Dim p As ThreadParams = CType(parms, ThreadParams) Dim de As DataExport.DataExporter = DataExporter.Instance Dim cRet As New NorthwindDataRetriever Dim cMethod As MethodBase = cRet.GetType().GetMethod("GetData", New Type() {parms.listType.GetType().MakeByRefType()}) Dim cArgs(0) As Object cMethod.Invoke(cRet, cArgs) AddHandler de.OnExportFinished, AddressOf de_OnExportFinished 'Option Strict On won't allow this line to compile. The Invoke method signature requires an argument 'of type System.Object(). Here, the Export method is looking for a List(Of T), which results in 'a narrowing conversion. (Base to derived is narrowing, because the base class only supports a 'subset of the interface that the derived class supports.) The requirement here is to implicitly 'recast cArgs(0) to List(Of T) (all possible values of parms.listType return such). We can't 'explicitly recast cArgs(0) in code because we don't know until runtime exactly which sort of T was 'passed into the current thread parameter. de.Export(cArgs(0), p.filePath, p.fileType) RemoveHandler de.OnExportFinished, AddressOf de_OnExportFinished Catch ex As NullReferenceException Console.WriteLine("Null Reference Exception in " + Thread.CurrentThread.Name + ": " + ex.Message + " (probable cause is incorrect object type in thread's listType parameter)") Exit Sub Catch ex As ArgumentException Console.WriteLine(ex.Message) Catch ex As Exception Console.WriteLine("Unhandled Exception: " & ex.Message) End Try End Sub Private Sub de_OnExportFinished(sender As iDataExporter, deArgs As aDEEventArgs) Select Case deArgs.ExportStatus Case 0 Console.WriteLine("Export routine for " + Thread.CurrentThread.Name + " failed with this error: " + deArgs.ReturnMessage) Case Else Console.WriteLine("Export to " + deArgs.FileName + " successful using " + Thread.CurrentThread.Name) End Select End Sub End Module |
NorthwindDataRetrieve.vb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
Option Strict On Imports NorthwindDataExporter.ODataNorthwindService Namespace NorthwindDataRetrieve Public Class Categories Public CategoryID As Integer Public CategoryName As String Public Description As String End Class Public Class ProductReorders Public ProductID As Integer Public ProductName As String Public UnitPrice As Decimal Public QuantityPerUnit As String Public UnitsInStock As Integer Public UnitsOnOrder As Integer Public ReorderLevel As Integer Public SupplierID As Integer Public SupplierName As String Public ContactName As String Public ContactPosition As String Public ContactPhone As String End Class Public Class SupplierProducts Public SupplierID As Integer Public SupplierName As String Public Products As List(Of SupplierProduct) End Class Public Class SupplierProduct Public ProductID As Integer Public ProductName As String Public UnitPrice As Decimal Public QuantityPerUnit As String Public UnitsInStock As Integer Public UnitsOnOrder As Integer Public ReorderLevel As Integer End Class Public Class NorthwindDataRetriever Dim dc As New NorthwindEntities(New Uri("http://services.odata.org/Northwind/Northwind.svc/")) Public Sub GetData(ByRef cList As List(Of Categories)) cList = dc.Categories.Select(Function(c As Category) New Categories With { .CategoryID = c.CategoryID, .CategoryName = c.CategoryName, .Description = c.Description } ).ToList() End Sub Public Sub GetData(ByRef cList As List(Of ProductReorders)) cList = ( From p In dc.Products Where p.UnitsOnOrder + p.UnitsInStock < p.ReorderLevel Order By p.Supplier.CompanyName, p.ProductName Select New ProductReorders With { .SupplierName = p.Supplier.CompanyName, .ContactName = p.Supplier.ContactName, .ContactPosition = p.Supplier.ContactTitle, .ContactPhone = p.Supplier.Phone, .ProductID = p.ProductID, .ProductName = p.ProductName, .UnitPrice = CDec(p.UnitPrice), .QuantityPerUnit = p.QuantityPerUnit, .UnitsInStock = CInt(p.UnitsInStock), .UnitsOnOrder = CInt(p.UnitsOnOrder), .ReorderLevel = CInt(p.ReorderLevel) } ).ToList() End Sub Public Sub GetData(ByRef cList As List(Of SupplierProducts)) cList = ( From s In dc.Suppliers Select New SupplierProducts With { .SupplierID = s.SupplierID, .SupplierName = s.CompanyName, .Products = ( From p In s.Products Select New SupplierProduct With { .ProductID = p.ProductID, .ProductName = p.ProductName, .UnitPrice = CDec(p.UnitPrice), .QuantityPerUnit = p.QuantityPerUnit, .UnitsInStock = CInt(p.UnitsInStock), .UnitsOnOrder = CInt(p.UnitsOnOrder), .ReorderLevel = CInt(p.ReorderLevel) } ).ToList() } ).ToList() End Sub End Class End Namespace |
DataExport.vb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
Option Strict On Imports System.Xml.Serialization Imports System.IO Imports System.Web.Script.Serialization Namespace DataExport Public Delegate Sub DataExporterEvent(sender As iDataExporter, deArgs As aDEEventArgs) Public Interface iDataExporter Sub Export(Of T As Class)(exportList As List(Of T), filePath As String, fileType As FileType) Event OnExportFinished As DataExporterEvent End Interface Public MustInherit Class aDEEventArgs Inherits EventArgs Public MustOverride ReadOnly Property ExportStatus() As Integer Public MustOverride ReadOnly Property ReturnMessage() As String Public MustOverride ReadOnly Property FileName() As String End Class Public Class DEEventArgs Inherits aDEEventArgs Private es As Integer Private rMsg As String Private fName As String Public Sub New(ExportStatus As Integer, ReturnMessage As String, FileName As String) es = ExportStatus rMsg = ReturnMessage fName = FileName End Sub Public Overrides ReadOnly Property ExportStatus As Integer Get Return es End Get End Property Public Overrides ReadOnly Property ReturnMessage As String Get Return rMsg End Get End Property Public Overrides ReadOnly Property FileName As String Get Return fName End Get End Property End Class Public NotInheritable Class DataExporter Implements iDataExporter Private Shared _instance As New DataExporter Public Event OnExportFinished As DataExporterEvent Implements iDataExporter.OnExportFinished Private Sub New() 'Override the default constructor to make it private End Sub Public Shared ReadOnly Property Instance() As DataExporter Get Return _instance End Get End Property Sub Export(Of T As Class)(exportList As List(Of T), filePath As String, fileType As FileType) Implements iDataExporter.Export Try Select Case fileType Case fileType.JSON Dim json As String With New JavaScriptSerializer json = .Serialize(exportList) End With File.WriteAllText(filePath, json) Case fileType.XML Dim serializer As New XmlSerializer(exportList.GetType()) Using writer As TextWriter = New StreamWriter(filePath) serializer.Serialize(writer, exportList) End Using Case Else Throw New Exception("FileType object fileType is improperly defined") End Select Dim deArgs As New DEEventArgs(1, "", filePath) RaiseEvent OnExportFinished(Me, deArgs) Catch ex As IOException Dim deArgs As New DEEventArgs(0, ex.Message, filePath) RaiseEvent OnExportFinished(Me, deArgs) Catch ex As Exception Dim deArgs As New DEEventArgs(0, "Unhandled Error: " & ex.Message, filePath) RaiseEvent OnExportFinished(Me, deArgs) End Try End Sub End Class End Namespace |
T-SQL: Various Examples
T-SQL examples, including transactions, dynamic SQL and various utilities.
T-SQL examples, including transactions, dynamic SQL and various utilities.
List all the tables and columns in the current database.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
SELECT DISTINCT tb.name 'Table' FROM sys.columns c join sys.tables tb on tb.object_id = c.object_id INNER JOIN sys.types t ON c.user_type_id = t.user_type_id LEFT OUTER JOIN sys.index_columns ic ON ic.object_id = c.object_id AND ic.column_id = c.column_id LEFT OUTER JOIN sys.indexes i ON ic.object_id = i.object_id AND ic.index_id = i.index_id ORDER BY tb.name |
Run a group of (parameterless) stored procedures one after the other (in this case, a series of daily merge procs).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
-- DECLARE @temp0 TABLE ( [ID] [int] IDENTITY(1,1) NOT NULL, [Name] [varchar](max) NULL ) INSERT INTO @temp0 SELECT Name FROM sys.procedures WHERE NAME LIKE 'usp_merge_%' DECLARE @spName varchar(max) DECLARE @Loops INT DECLARE @X INT DECLARE @return_value int SET @Loops = (SELECT COUNT(*) cnt FROM @temp0) SET @X = 1 WHILE @X <= @Loops BEGIN SET @spName = (SELECT Name FROM @temp0 WHERE ID = @x) EXEC @return_value = @spName SELECT 'Return Value' = @return_value, 'Name' = @spName SET @X = @X + 1 END |
Pull data from a list of by-the-minute inspection results, roll them up on a per-hour basis, and pivot the results based on inspection type.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
SELECT ProcessDate, [Abnormality], [ILX], [Balancer] INTO DataByHour FROM ( SELECT DISTINCT ProcessDate, ProcessType, COUNT(ProcessDate) AS TiresProcessed FROM ( SELECT [MachineID] --Pulls data rolled up by hour (Adding 30 mins has the effect of rounding to nearest hour) ,DATEADD(HOUR, DATEDIFF(HOUR, 0, DATEADD(MI, 30, [ProcessDate])), 0) AS ProcessDate --,DATEADD(DAY, DATEDIFF(DAY, 0, [ProcessDate]), 0) AS ProcessDate --This one will pull data rolled up by day ,[ProcessResult] ,[ProcessType] FROM [Final].[dbo].[vw_InspectionResults] WHERE ProcessDate > '09/11/2014' ) AS X GROUP BY ProcessDate, processtype ) AS Y PIVOT (SUM(TiresProcessed) FOR ProcessType IN ([Abnormality], [ILX], [Balancer])) AS Z |
Use a transaction and dynamic SQL to perform an atomic read/write operation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
/* This stored procedure accepts a comma-separated list of hospitalids as a string, finds the lowest available internalid value in the hospid_intid table, updates (in the hospid_intid table) each hospitalid in the string with the new internalid, and outputs the new internalid to the calling procedure. The read and update need to be atomic, so they are enclosed in a transaction. We also need a guaranteed clean read. Since TRAN default behavior is to allow dirty reads and disallow dirty writes, we have to lock the hospid_intid table for the duration of the transaction. After finding the lowest available internalid value, the procedure plugs the resulting number into the update query and uses dynamic SQL to execute it. */ CREATE PROCEDURE [dbo].[UpdateInternalId] @HospitalList varchar(MAX), @InternalID int OUT AS SET NOCOUNT ON DECLARE @SQL varchar(MAX) SET XACT_ABORT ON BEGIN TRAN SELECT @InternalID = ( SELECT TOP 1 a.internalid + 1 FROM hospid_intid a WITH (tablockx, holdlock) --Lock the table WHERE NOT EXISTS ( SELECT 1 FROM hospid_intid b WHERE b.internalid = a.internalid + 1 ) ORDER BY a.internalid ) SET @SQL = 'UPDATE hospid_intid SET internalid = ' + Convert(VarChar(10), @InternalID) +'WHERE hospitalid IN (' + @HospitalList + ')' EXEC (@SQL) COMMIT TRAN SET XACT_ABORT OFF |
List all reports in SSRS database by path, name and schema
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
USE ReportServer SELECT RPT.Path ReportPath ,RPT.name ReportName ,SUBSTRING ( CONVERT ( VARCHAR(MAX) ,CONVERT ( XML ,CONVERT ( VARBINARY(MAX) ,RPT.Content ) ) ) ,PATINDEX ( '%xmlns="%' ,CONVERT ( VARCHAR(MAX) ,CONVERT ( XML ,CONVERT ( VARBINARY(MAX) ,RPT.Content ) ) ) )+7 ,PATINDEX ( '%">%' ,CONVERT(VARCHAR(MAX) ,CONVERT ( XML ,CONVERT ( VARBINARY(MAX) ,RPT.Content ) ) ) ) - PATINDEX ( '%xmlns="%' ,CONVERT ( VARCHAR(MAX) ,CONVERT ( XML ,CONVERT ( VARBINARY(MAX) ,RPT.Content ) ) ) ) -7 ) ReportSchema FROM ReportServer.dbo.[Catalog] RPT WHERE RPT.Type = 2 |
VB6: Using CBT Hooking to Change the Layout of the Native MsgBox Function
The Computer-Based Training, or CBT, API calls give the ability to automate screen functions in response to user actions, and in so doing create the ability for automated training modules to interact with students. Leveraging these functions allows a low-level event-based model, whereby a developer can use a hook to customize handling of screen-related activities.
This code allows font changes and various other format customizations of the standard VB6 MsgBox dialog box.
The Computer-Based Training, or CBT, API calls give the ability to automate screen functions in response to user actions, and in so doing create the ability for automated training modules to interact with students. Leveraging these functions allows a low-level event-based model, whereby a developer can use a hook to customize handling of screen-related activities.
This code allows font changes and various other format customizations of the standard VB6 MsgBox dialog box. It uses a CBT hook to intercept an internal window call, in this case, the window call generated by the MsgBox function. Upon intercepting the call it gets a handle to the MsgBox window as well as its various child windows (the label containing the message text, any buttons, and an icon if it exists). It then resizes the window to accommodate altered sizing, and repositions the icon and any command buttons. Finally, it positions the resized MsgBox window in the center of the screen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
'General Note: notes are above the line of code to which they apply. Option Explicit 'Window size and position constants Private Const ICON_WIDTH As Integer = 32 Private Const BTN_WIDTH As Integer = 75 Private Const BTN_HEIGHT As Integer = 23 Private Const BTN_SPACER As Integer = 6 'Space between 2 buttons Private Const STW_OFFSET As Integer = 12 'Standard window offset: minimum window offset from the edge of 'its container 'SendMessage constants that we will use Private Const WM_SETFONT = &H30 Private Const WM_GETTEXT = &HD 'Constants required for CBT hooking Private Const HCBT_CREATEWND = 3 Private Const HCBT_ACTIVATE = 5 Private Const WH_CBT = 5 'Working variables that require module-wide scope Private hHook As Long Private myFont As IFont Private cPrompt As String Private hwndStatic As Long Private ButtonHandles() As Long Private xPixels As Long Private yPixels As Long Private isIcon As Boolean 'The API Type declarations we need Private Type SIZE cx As Long cy As Long End Type Private Type RECT Left As Long Top As Long Right As Long Bottom As Long End Type Private Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" _ (ByVal idHook As Long, ByVal lpfn As Long, ByVal hmod As Long, ByVal dwThreadId As Long) As Long Private Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As Long) As Long 'GETTEXT needs a String argument for lParam and SETFONT needs an Any argument there, so we "overload" 'SendMessageA by providing two declarations Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _ (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long Private Declare Function SendMessageS Lib "user32" Alias "SendMessageA" _ (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As String) As Long Private Declare Function SetWindowPos Lib "user32" _ (ByVal hwnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, _ ByVal wFlags As Long) As Long Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" _ (ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long Private Declare Function EnumChildWindows Lib "user32" _ (ByVal hWndParent As Long, ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long Private Declare Function GetClientRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long 'Wrapper for the normal MsgBox function. The x and y arguments are optional and are in twips. If not 'specified, myMsgBox will use default window sizes and positions. These work fine with default font sizes, 'but usually don't with other sizes. Public Function myMsgBox(Prompt As String, Buttons As VbMsgBoxStyle, ByVal fSize As Integer, _ ByVal fBold As Boolean, ByVal fItalic As Boolean, ByVal fULine As Boolean, fFaceName As String, _ Optional Title As String, Optional HelpFile As String, Optional Context As Long, Optional x As Long, _ Optional y As Long) As Long cPrompt = Prompt Set myFont = New StdFont With myFont 'We can make whatever adjustments we like here to the font .SIZE = fSize .Bold = fBold .Italic = fItalic .Underline = fULine .Name = fFaceName End With 'Convert x and y arguments to pixels from twips. (Twips are the same size no matter the screen 'resolution; pixels aren't.) If Not IsMissing(x) Then xPixels = Int(x / Screen.TwipsPerPixelX) End If If Not IsMissing(y) Then yPixels = Int(y / Screen.TwipsPerPixelY) End If 'Set up the hook to catch windows messages, call CBTProc when there is one hHook = SetWindowsHookEx(WH_CBT, AddressOf CBTProc, App.hInstance, 0) 'This will call CBTProc, passing the handle of the MsgBox window to the wParam argument. myMsgBox = MsgBox(Prompt, Buttons, Title, HelpFile, Context) End Function Private Function CBTProc(ByVal lMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long Dim i As Integer Dim statX As Integer 'X dimension of static (text) window Dim statY As Integer 'Y dimension of same Dim cLeft As Integer 'Current Left value for current button, used to position buttons along x axis Dim rc As RECT 'Used with GetClientRect If lMsg = HCBT_ACTIVATE Then 'Immediately unhook (we have the handle we're looking for, and don't want to fire any more CBT 'events) UnhookWindowsHookEx hHook 'Call EnumChildWindowProc once for each window that is contained in the MsgBox EnumChildWindows wParam, AddressOf EnumChildWindowProc, 0 'Reinitialize the static buttoncount variable, see the proc EnumChildWindowProc 0, 1 'Should always be true, but this prevents an abend if for some reason we fail to get the text 'window. If hwndStatic Then 'If the x parameter has been supplied to the main wrapper, then xPixels <> 0 If xPixels <> 0 Then 'Center the MsgBox window in the screen With Screen SetWindowPos wParam, 0, (.Width / .TwipsPerPixelX - xPixels) / 2, _ (.Height / .TwipsPerPixelY - yPixels) / 2, xPixels, yPixels, 0 End With 'Analogous to the ScaleWidth and ScaleHeight properties. Client rectangle's dimensions 'are returned to the rc type and exclude the dimensions of the title bar and the borders. GetClientRect wParam, rc 'Calculate x and y values for text window. If there's an icon, we need to reduce the size 'of the text window by the width of the icon plus a standard offset value. statX = rc.Right - rc.Left - STW_OFFSET * 2 - ((isIcon And 1) * (ICON_WIDTH + STW_OFFSET)) statY = rc.Bottom - rc.Top - BTN_HEIGHT - STW_OFFSET * 2 'We need to position the text window along the x axis such that it's a standard offset from 'the left border ofthe msgbox, plus the width of the icon and another standard offset if 'the icon exists. SetWindowPos hwndStatic, 0, STW_OFFSET + (isIcon And 1) * (ICON_WIDTH + STW_OFFSET), _ STW_OFFSET, statX, statY, 0 isIcon = 0 'Loop through the button handles, calculating the left border position each time. For i = 0 To UBound(ButtonHandles) 'Current left border is half the container window's width, less the width of half the 'total number of buttons, plus the offset of the current button in the array. cLeft = Int(xPixels / 2 + BTN_WIDTH * (i - (UBound(ButtonHandles) + 1) / 2)) 'Modify the above to add button spacer widths. cLeft = cLeft + BTN_SPACER * (i - (UBound(ButtonHandles) - 1) + (UBound(ButtonHandles) Mod 2) / 2) 'The Y value is 1 standard offset more than the height of the text window. SetWindowPos ButtonHandles(i), 0, cLeft, statY + STW_OFFSET, BTN_WIDTH, BTN_HEIGHT, 0 Next End If SendMessage hwndStatic, WM_SETFONT, myFont.hFont, True End If End If CBTProc = 0 'allow operation to continue End Function Private Function EnumChildWindowProc(ByVal hChild As Long, ByVal lParam As Long) As Long Static ButtonCount As Integer Dim sLen As Integer Dim wClass As String Dim wText As String Dim rc As RECT If lParam Then ButtonCount = 0 'See the direct call of this proc in CBTProc: resets the ButtonCount Exit Function 'variable to 0 End If wClass = String(64, 0) 'look up the type of the current window sLen = GetClassName(hChild, wClass, 63) wClass = Left(wClass, sLen) 'We have either one or two static windows: optionally the icon (the first window if it's there) and the 'text window (analogous to a label control). If wClass = "Static" Then 'Find out whether the current window's text value is the same as the text passed in to the cPrompt 'argument in the main wrapper function. If it is, it's the text window and we store the handle 'value in hwndStatic. If it isn't, then it's an icon and we set the isIcon flag. If Not hwndStatic Then wText = String(Len(cPrompt) + 1, 0) sLen = SendMessageS(hChild, WM_GETTEXT, 255, wText) wText = Left(wText, sLen) If wText = cPrompt Then hwndStatic = hChild Else isIcon = True End If End If ElseIf wClass = "Button" Then 'Store the button's handle in the ButtonHandles array ReDim Preserve ButtonHandles(ButtonCount) ButtonHandles(ButtonCount) = hChild ButtonCount = ButtonCount + 1 End If EnumChildWindowProc = 1 'Continue enumeration End Function |
VB6: Accessing the RichTextBox Control’s Underlying iRichTextOLE
Interface
This code shows a way to work around the limitations of the VB6 RichTextBox control, accessing its underlying
iRichTextOLE
interface to implement support for later versions of the Text Object Model (TOM) than the one directly supported by the control.
This code shows a way to work around the limitations of the VB6 RichText control, accessing its underlying
iRichTextOLE
interface to implement support for later versions of the Text Object Model (TOM) than the one directly supported by the control.The reason that the RichTextBox control doesn’t support much of the Rich Text specification is that it only directly supports RTF version 1.0, in part due to backward compatibility concerns. However, with a little sleight of hand, it is possible to get the RichTextBox control to use the latest RTF version (currently 4.1), thereby greatly expanding the usable feature set. You can do this by accessing the TOM interface directly.
First, along with the RichTextBox reference, we’ll need to add a reference to TOM (if not listed as an available reference — it usually isn’t — browse to riched20.dll and select it). Of course, we’ll also need a Form with a RichTextBox control on it (in this example, named
rtcMyControl
).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _ (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long Private Const WM_USER = &H400& Private Const EM_GETOLEINTERFACE = (WM_USER + 60) 'This reference can point to any COM object, since by definition all COM objects inherit from IUnknown. Dim myIUnknown As IUnknown 'The ITextDocument object is the top-level object in the TOM. Dim tomDoc As ITextDocument Sub Form_Load() 'Use SendMessage to get a reference to the RichTextBox control's OLE interface (iRichEditOle). The 'iRichEditOle interface exposes the underlying COM interface (which is an implementation of RTF v4.1) 'of a RichTextBox control. SendMessage(rtcMyControl.hwnd, EM_GETOLEINTERFACE, 0&, myIUnknown) 'Assign the reference to the iUnknown object. This causes a call to iUnknown's QueryInterface method, 'which will return ITextDocument as one of the interfaces it supports and therefore allow the reference 'to be added, exposing the functionality of the ITextDocument interface through the tomDoc variable. tomDoc = myIUnknown End Sub |
All this is necessary because the line
Dim tomDoc as iRichEditOle
fails to compile, even thoughiRichEditOle
is a COM interface. The reason that it fails to compile is thatiRichEditOLe
doesn’t implement a dual interface, that is, it implementsiUnknown
as all COM classes do, but does not implementiDispatch
.VB’s direct support of COM is limited to those COM classes that implement both interfaces. VB directly supports only those interfaces that allow late binding, and late binding requires implementation of
iDispatch
. Nevertheless, as this example shows, VB can access any COM object through the use of API techniques similar to this one.
Blackjack Diagrams
This is a collection of diagrams that model different aspects of the Ruby Blackjack game.
This collection of diagrams models different aspects of the Blackjack game. There is a class diagram and three flowcharts: an overall flow model, a calculate hand subprocess and a calculate 21 subprocess.
Class Diagram
Main Flowchart
Subroutine: Calculate Hand Total
Subroutine: Check for Natural 21
Mixing BSCS: Preprocessing Logic
The Mixing Business Support Control System controls a rubber mixing machine that is part of an overall process of manufacturing tires. This diagram shows the preprocessing logic, which verifies that an authorized employee has selected one of two correct ingredient specifications for the mixing machine.
The Mixing Business Support Control System controls a rubber mixing machine that is part of an overall process of manufacturing tires. This diagram shows the preprocessing logic, which verifies that an authorized employee has selected one of two correct ingredient specifications for the mixing machine.
Mixing BSCS: Process Barcode Scan
The Mixing Business Support Control System controls a rubber mixing machine that is part of an overall process of manufacturing tires. This diagram shows all the checks that are made to a bale of rubber in the input queue before allowing it to be introduced into the mixer.
The Mixing Business Support Control System controls a rubber mixing machine that is part of an overall process of manufacturing tires. This diagram shows all the checks that are made to a bale of rubber in the input queue before allowing it to be introduced into the mixer.
PD Manager: Wireframe Sketch of Field Contact Entry Form
This is a wireframe sketch of a field contact report entry. A field contact is an interaction between a police officer and a citizen while on patrol, either because the circumstances appear suspicious, or because the officer is looking for information about a specifically identified situation.
This is a wireframe sketch of a field contact report entry. A field contact is an interaction between a police officer and a citizen while on patrol, either because the circumstances appear suspicious, or because the officer is looking for information about a specifically identified situation.
PPLS: Process Vendor RFQs
This diagram shows the flow of work for generating Vendor RFQs (Requests for Quote) for multiple open DIBBS (Defense Logistics Agency Internet Bulletin Board System) solicitations for a single NSN (National Stock Number). A Base RFQ joins a group of solicitations for one NSN to a group of vendor RFQs for parts conforming to that NSN; this diagram shows the process from the context of a single base RFQ/single NSN.
This diagram shows the flow of work for generating Vendor RFQs (Requests for Quote) for multiple open DIBBS (Defense Logistics Agency Internet Bulletin Board System) solicitations for a single NSN (National Stock Number). A Base RFQ joins a group of solicitations for one NSN to a group of vendor RFQs for parts conforming to that NSN; this diagram shows the process from the context of a single base RFQ/single NSN.
HealthCompare.com: Click-Through Commission Technical Design
This is a technical design document for an enhancement to an online enrollment system application. The enhancement enables the awarding of click-through commissions to linked websites.
This is a technical design document for an enhancement to an online enrollment system application. The enhancement enables the awarding of click-through commissions to linked websites.
UML: Documenting System Architecture and Behavior
This document contains a sampling of diagrams and other artifacts that document a software system. Taken together, the items illustrate an approach to documenting a problem domain, from a high-level overview down to details of architecture and behavior.
This document contains a sampling of diagrams and other artifacts that document a software system. Taken together, the items illustrate an approach to documenting a problem domain, from a high-level overview down to details of architecture and behavior.
AP Style Guide
A guide for writers and editors working with AP style to use as a reference.
A guide for writers and editors working with AP style to use as a reference.
AP Style and Punctuation Guide
Abbreviations
- Using periods in abbreviations
- Do not use periods in abbreviations of three or more words: NCAA, USC, UCLA.
- If the abbreviation would otherwise spell a word, then use periods: c.o.d..
- In general, use periods in abbreviations of two words: U.S., U.K., U.N. However, there are numerous exceptions such as AP, IT and GI. (When in doubt, use periods. If you see something all the time without periods, don’t use them.)
- A sportswriting exception: do not use periods in two-word abbreviations of universities: OU, ND.
- Do not use periods in abbreviations of three or more words: NCAA, USC, UCLA.
- Dates
- Do not abbreviate days of the week.
- When a month is on its own or with just a year, don’t abbreviate it: My birthday is in September. I was born in September 1976.
- When a month is used with a specific day, abbreviate those with more than five letters (all months except March through July). Use the first three letters of the month to abbreviate: My birthday is on Sep. 15.
- Symbols
- Always spell out cents and percent (don’t use ¢ or %). Always use numerals (don’t write out the number) with cents and percent: 5 cents, 1 percent, 25 cents, 100 percent.
- Use $ if it accompanies a number: $5 (not $5.00),$49.95, $1 million.
- Use & (ampersand) only if it is part of an organization name: Texas A&M.
- In particular, avoid using & in titles for technical reasons: Google can interpret it as code. If you find that you have to use an ampersand in a title, such as with Texas A&M, take care that there is no space before or after it.
- Abbreviate “number” as No., and do not write out any numbers you use: No. 1, No. 84. (In case you were ever curious, this abbreviation comes from the French word for number, which is numéro.)
- Personal Titles
- For coaches, always use Coach as a title, if you use any title at all. Do not use HC, OC or DC. Those are job descriptions rather than titles, analogous to player positions such as QB and LB.
- Certain personal titles are abbreviated when given along with a person’s name: Dr. Marcus Welby, Gen. Robert E. Rodes. Certain are not: President Abraham Lincoln, Vice President Andrew Johnson. (For an exhaustive list of what titles to abbreviate and when, see http://writingexplained.org/ap-style/ap-style-titles, in particular the several links in the Additional Guidance section at the bottom.)
- Player and Coaching Positions
- Do not use HC, OC or DC as abbreviations for head coach, offensive coordinator and defensive coordinator, unless you are writing a coach’s opinion or analysis article and referring to the job description frequently. (In such a case, you may abbreviate any but the first use in your article.) Keep in mind that these are job descriptions rather than titles, and therefore are not to be capitalized when not abbreviated.
- Do not capitalize player positions such as quarterback.
-
- It is acceptable to use the standard one- or two-letter abbreviations for player positions, if you are using them frequently in an article. Write out the position on first use.
- Football plays
- When discussing a play in an analysis article, write it out the first time, then use common abbreviations in subsequent references if you prefer. Capitalize the abbreviations: RPO, QBS.
- Streets and Street Addresses
- If there is no street address, spell out the street name and direction (North, West, etc.): The city will close West Main Street.
- An exception is when the street name is a number. In this case, apply the rules for writing numbers: East 75th Street, Fifth Avenue.
- If using the entire street address, abbreviate everything you can, using periods in all cases: 5 W. 75th St., 846 Park Ave.
- However, do not abbreviate Circle, Court, Drive or Road.
- If there is no street address, spell out the street name and direction (North, West, etc.): The city will close West Main Street.
- U.S. States
- Do not abbreviate states when they appear alone: The Ducks left for southern California yesterday.
- Do not abbreviate states with five or fewer letters: Idaho, Iowa, Maine, Ohio, Texas and Utah.
- Do not abbreviate Alaska or Hawaii.
- Abbreviate all other states when they appear in conjunction with the name of a city, town, village or military base: Eugene, Ore.., Fort Sill, Okla.
- When abbreviating, do not use the ZIP code two-letter abbreviations. Use the standard state abbreviations:
Ala. Ariz. Ark. Calif. Colo. Conn. Del. Fla. Ga. Ill. Ind. Kan. Ky. La. Mass. Md. Mich. Minn. Miss. Mo. Mont. N.C. N.D.. N.H. N.J. N.M. N.Y. Neb. Nev. Okla. Ore. Pa. R.I. S.C. S.D.. Tenn. Vt. Va. W.Va. Wash. Wis. Wyo. Ala. Ariz. Ark. Calif. Colo. Conn. Del. Fla. Ga. Ill. Ind. Kan. Ky. La. Mass. Md. Mich. Minn. Miss. Mo. Mont. N.C. N.D.. N.H. N.J. N.M. N.Y. Neb. Nev. Okla. Ore. Pa. R.I. S.C. S.D.. Tenn. Vt. Va. W.Va. Wash. Wis. Wyo.
- U.S. Cities
- Do not abbreviate any part of U.S. city names, even if they contain words that are often abbreviated in street names: South Bend, Ind.; North Platte, Neb..
- The lone exception to this is always to abbreviate the word Saint: St. Louis, St. Paul, Minn..
-
- Include the state with the city, except with these major cities:
Atlanta Baltimore Boston Chicago Cincinnati Cleveland Dallas Denver Detroit Honolulu Houston Indianapolis Las Vegas Los Angeles Miami Milwaukee Minneapolis New Orleans New York Oklahoma City Philadelphia Phoenix Pittsburgh St. Louis San Diego San Francisco Seattle Salt Lake City San Antonio Washington Atlanta Cleveland Houston Milwaukee Philadelphia San Antonio Baltimore Dallas Indianapolis Minneapolis Phoenix San Diego Boston Denver Las Vegas New Orleans Pittsburgh San Francisco Chicago Detroit Los Angeles New York St. Louis Seattle Cincinnati Honolulu Miami Oklahoma City Salt Lake City Washington Atlanta Cleveland Houston Milwaukee Philadelphia San Antonio Baltimore Dallas Indianapolis Minneapolis Phoenix San Diego Boston Denver Las Vegas New Orleans Pittsburgh San Francisco Chicago Detroit Los Angeles New York St. Louis Seattle Cincinnati Honolulu Miami Oklahoma City Salt Lake City Washington
- Include the state with the city, except with these major cities:
Capitalization
- Always capitalize the first word of a sentence. This rule overrides all other capitalization rules.
- Capitalize proper nouns (names of specific persons, places or things): Notre Dame Fighting Irish, Marcus Freeman, Sacred Heart Basilica.
- There are a few exceptions, usually brand names such as eBay. (Avoid putting those exceptions at the beginning of a sentence, as you will have to capitalize them.)
- If a common noun is part of a name, do not capitalize it if it’s in plural form (when referring to more than one of the same type of thing): the Ohio and Mississippi rivers, lakes Erie and Ontario, Autzen and Reser stadiums. An exception to this is if a common noun is part of a nickname for a sports team: The Chicago Bears, The Oregon Ducks. In this case, capitalize the word even when using only the nickname: The Bears, The Ducks.
- For more capitalization rules for names of persons, see Names of Persons below.
- Sports Coaches, Players and Teams
- Do not capitalize head coach, offensive coordinator, defensive coordinator, etc. and do not use them as titles. Use Coach (capitalized) for the title of any coach: Coach Marcus Freeman rather than Head Coach Marcus Freeman. Head coach is a job description rather than a title: Notre Dame’s new head coach, Marcus Freeman, arrived in South Bend yesterday.
- Do not capitalize player positions such as quarterback.
- If you are using an abbreviation for a player or coach, capitalize it: HC, OC, DC, QB, LB, WR, etc.
- Do not capitalize a player’s academic year: senior quarterback Patrick Mahomes.
- Do not capitalize terms such as defensive line, red zone or offensive line.
- Do not capitalize names of plays: dive play, off tackle, run-pass option, quarterback sneak.
- For more information on AP style as specifically applied to football, see http://www.ap.org/press-releases/2012/ap-compiles-super-bowl-style-guide.
- Compass points
- Capitalize regions that use a compass point: The Southeast is a major recruiting area.
- Capitalize names of areas that are equivalent to proper names: London’s East End neighborhood.
- Do not capitalize directions that use a compass point: Tornadoes generally move northeast.
- Capitalize words that derive from a proper noun when the noun is part of the meaning: Shakespearean, Socratic. Do not capitalize words deriving from a proper noun if they no longer depend on the noun for their meaning: english (when referring to spin on a ball in billiards and pool), french fries, herculean.
- Capitalize awards and decorations: the Heisman Trophy, the National Championship Trophy, the Dick Butkus Award.
- Capitalize family words when they are used as a substitute for a person’s name: Mom and Dad came to visit the campus. Otherwise, do not capitalize them: My mother and father came to visit the campus.
- Capitalize names of specific animals: Bevo the steer, Uga the bulldog. Capitalize proper nouns in animal breeds: Uga is an English bulldog, Bevo is a Texas longhorn steer.
- However, Bevo is the Texas Longhorns’ steer is correct. Texas Longhorns is a proper name, therefore Longhorns is capitalized.
- Do not capitalize plant names unless they contain a proper noun: white pine, Sitka spruce, Douglas fir.
- Do not capitalize seasons unless they are part of a proper name: spring ball, the Oregon Spring Game.
Dates and Times
- Dates
- Use numbers for days: Jan. 1 rather than Jan. 1st.
- Write out days of the week in full.
- Capitalize months.
- When a month is on its own or with just a year, don’t abbreviate it: My birthday is in September. I was born in September 1976.
- When a phrase lists only a month and year, do not separate the month and the year with commas: February 1980 was his best month.
- When a month is used with a specific day, abbreviate those with more than five letters (all months except March through July). Use the first three letters of the month to abbreviate: My birthday is on Sep. 15.
- When a date has a month, day and year, set off the year with commas: Sep. 4, 2024, is a day we are all waiting for.
- Times
- Use numerals for all times, along with a.m. or p.m.: Our meeting was at 2:30 p.m. Do not use zeros with times that are on the hour: 2 p.m. rather than 2:00 p.m.
- Exceptions:
- Do not use 12 a.m. or 12 p.m., as these are inaccurate (a.m. and p.m. are abbreviations for the Latin ante meridiem and post meridiem, meaning before midday and after midday, respectively). Use noon and midnight (without using 12) instead. If you need to abbreviate them (for example, you have a table of hourly times), you may use 12 n. and 12 m.
- When using the word o’clock, standard rules for numbers apply: four o’clock, 10 o’clock.
- If the time includes minutes, separate the hour and minutes with a colon: 12:45 p.m.
- If the time includes seconds, separate them from the minutes with a colon as well: The current world record in the men’s marathon is 2:02:57. Neil Armstrong took his first step on the moon on July 21, 1969, at 10:56:12 p.m. EDT.
Italics
-
Italicize the titles of complete works. The titles of comparatively short works — songs, short poems, essays, and episodes of TV programs — should be enclosed in quotation marks instead.
Some examples:
-
- Books: Catch-22, by Joseph Heller
- Magazines and journals: Time
- Newspapers: The Times
- Plays: A Raisin in the Sun, by Lorraine Hansberry
- Movies: The Godfather
- Television programs: Doctor Who
- Works of art: Nighthawks, by Edward Hopper
- Albums and CDs: OK Computer, by Radiohead
- Italicize the names of aircraft, ships and trains, foreign words used in an English sentence, and words and letters discussed as words and letters. For example:
- “These are the voyages of the starship Enterprise.” (Title sequence of the original Star Trek TV series)
- From 1925 to 1953, a passenger train named the Orange Blossom Special brought vacationers to sunny Florida from New York.
- “There is no danger that Titanic will sink. The boat is unsinkable and nothing but inconvenience will be suffered by the passengers.” (Phillip Franklin, Vice President of White Star Line)
- “Come kiss me, and say goodbye like a man. No, not good-bye, au revoir.” (William Graham, “Chats With Jane Clermont,” 1893)
- “Every word she writes is a lie, including and and the.” (Mary McCarthy on Lillian Hellman)
- Use italics to emphasize words and phrases:
- “I don’t even like old cars. I mean they don’t even interest me. I’d rather have a … horse. A horse is at least human, for God’s sake.” (J. D. Salinger, The Catcher in the Rye)
- However, it is wise to keep this in mind: “Think of italics as butterflies that might swoop across the page, allow them to flit about, land here and there, softly; gently; don’t treat them as a blanket that must spread itself across the entire page. The butterfly approach will bring a dash of color; the blanket approach will darken everything.” (William Noble, Noble’s Book of Writing Blunders (and How to Avoid Them), Writer’s Digest Books, 2006)
Names of Persons
- The first time you refer to a person in an article, use the person’s full name, as well as any appropriate title. Thereafter, use only the person’s last name.
- In particular, do not refer to a coach as Coach in any references but the first one (where it is optional), unless the reference is part of a quote. Again, just use the last name in references subsequent to the first one.
- However, you may use the first name (and any appropriate title) again as part of a summary paragraph at the end of the article.
- Do not use Mr., Mrs., Ms. or Miss with names, unless they are part of a quote.
- Do not precede Sr. or Jr. with a comma: Mike Sanford Sr., Mike Sanford Jr.
- It’s important to distinguish between a personal title and a job description. These are titles: Gen. Robert E. Rodes, Dr. Marcus Welby. If you are using the title as part of a job description, don’t abbreviate or capitalize it: Confederate general Robert E. Rodes, television doctor Marcus Welby.
Numbers
The cardinal numbers (one, two, three, etc.) zero through nine are written out, as are the ordinal numbers (first, second, third, etc.) first through ninth. and above are written as numerals: 10, 11, 12; 10th, 11th, 12th.
Some exceptions to these rules are:
- Write out a number when it begins a sentence: Twenty-four years ago, I bought my house. Two exceptions to this exception:
- When citing a year at the beginning of a sentence, use the numeral: 2016 is the first year that Washington has beaten Oregon since 2003.
- When citing a score at the beginning of a sentence, use the numeral: 24-22 was a much closer score than expected.
- When citing a score involving a number less than 10, use the numeral: Oregon beat Old Dominion by a score of 58-3. (Spell out a number less than 10 if you don’t use a hyphen: a score of 58 to three.)
- Always write an age as a number, when you are using just the number: Frank had two boys: Joe, 5 and James, 3.(However, Joe was five years old is correct.)
- If the age is less than one year, the normal rules for numbers apply: six months, 11 months.
- Write out numbers in proper names: Final Four.
- Use the numeral for all numbers except zero when writing a series of statistics: Freeman had 185 yards on 18 carries, 2 touchdowns and zero fumbles. (Of course, you could say no fumbles here if you prefer.)
- Use the numeral in most sports-specific expressions involving numbers: 4-star recruit, 3rd and 9, 4th down, 5-yard line, 3-point shot, par 4, 3-1 odds. However, when using ordinal numbers in a compound adjective (which is hyphenated) it is acceptable to write them out. For example, feel free to use fourth-down yardage instead of 4th-down yardage, if it looks better to you.
- When referring to weeks of a season use the numeral: week 1 rather than week one.
- Do not spell out monetary numbers, and do not add zeros for whole-dollar amounts: $6 rather than $6.00 or six dollars.
- An exception: write out expressions such as a dollar, a couple of dollars, two or three dollars. (Make a dollar or two would look very funny if written make $1 or $2.)
- Write out numbers contained in well-known expressions: a picture is worth a thousand words, if I had a million dollars, an eleventh-hour comeback, a high-five.
For a list of quite a few other exceptions, see http://writingexplained.org/ap-style/ap-style-numbers.
Punctuation
- Exclamation Point
- Be careful not to overuse exclamation points. Use them to indicate something surprising or something that stands out significantly from the surrounding context.
- Do not double exclamation points or combine them with other punctuation such as question marks.
- Use them only at the end of a complete sentence, unless they are part of a quotation (for more information on combining exclamation points and quotation marks, see the section on quotation marks).
- If you are using an informal tone, you may use one in parentheses (!) to indicate surprise or amusement at something that you are saying.
- Period
- A period ends a sentence, or indicates an abbreviation.
- For more information on how to combine periods and quotation marks, see the section on quotation marks.
- Question Mark
- Use a question mark only when the sentence asks a question.
- In particular, avoid using a question mark to indicate a “dramatic pause”: If you don’t submit your article on time? You will be beheaded.
- Use a question mark only at the end of a complete sentence, unless it is part of a quotation (for more information on combining question marks and quotation marks, see the section on quotation marks).
- Use a question mark only when the sentence asks a question.
-
Apostrophe
Apostrophes are the cause of many spelling errors. It’s important to have a thorough understanding of how to use (and how not to use) them.
- Apostrophes are used in three situations:
- To indicate letters left out in a contraction: isn’t, you’re, we’d, etc.
- To indicate the possessive case with a common or proper noun, with s: Oregon’s team, the team’s players.
- To indicate the plural of a single letter, with s: mind your p’s and q’s, this sentence contains a total of six t’s.
- When using an apostrophe with the possessive case, these rules apply:
- Singular noun: add ’s to the word: Oregon’s schedule, the team’s schedule.
- Plural noun ending in s: add just an apostrophe to the word: the Ducks’ schedule, the Pac-12 teams’ schedules, girls’ basketball.
- Irregular plural noun not ending in s: same rule as a singular noun: women’s basketball, children’s music.
- The rules for singular nouns that end in s are a bit complicated. The Chicago Manual of Style suggests treating them the same as any other singular noun: the Jones’s car, the boss’s office. AP Style’s rules are more complicated, and not always adopted:
- If the noun is a proper noun, just add an apostrophe: the Jones’ car.
- If the noun is a common noun, add ’s: the boss’s office.
- In rare cases where the s isn’t pronounced in a common expression, omit it from the spelling: for goodness’ sake, but for completeness’s sake.
- Apostrophes are often misused. Here are some typical mistakes to avoid:
- Do not use an apostrophe in plurals of proper nouns ending in s: keeping up with the Joneses, not keeping up with the Jones’s (or Jones’). This is easy to confuse because of the Jones’s house (i.e. the house belonging to the Joneses, a possessive).
- Do not use an apostrophe to indicate the plural of an abbreviation: RBIs, INTs, CDs rather than RBI’s, INT’s, CD’s.
- Do use an apostrophe to indicate a plural reference to a letter: mind your p’s and q’s, dot all the i’s and cross all the t’s.
- Apostrophes are used in three situations:
-
- Do not use apostrophes to indicate a decade: 1990s rather than 1990’s.
- However, ’90s is correct, because in this case you are using the apostrophe to indicate numbers left out. Either ’90s or 90s is acceptable.
- Do not confuse the plural of a regular common noun with the possessive of same. None of the apostrophes in this sentence are correct: Some writer’s have a nervous habit of using apostrophe’s to indicate plural’s of regular common noun’s. (To correct this sentence, simply remove all of the apostrophes.)
- Finally, do not confuse its and it’s. Its is a possessive pronoun, comparable to his, her, your and their. It’s is a contraction of the words it is, and the apostrophe indicates that the i in the word is is omitted.
- Comma
- When writing numerals, use a comma with a four-digit number: 1,025.
- Exceptions are years: 2017, street addresses: 15650 Arthur St. and page numbers.
- Set off degrees and certifications with a comma after a person’s name: John Jones, Ph.D. or Marcus Welby, M.D.
- Set off direct addresses of persons or groups with commas: Thank you, Mr. and Mrs. Jones, for your kind donation. Just think, fellow Scouts, what we will be able to do with these funds. Friends, we will be looking for your ideas in next week’s meeting.
- With dates, these rules apply:
- Separate day, date and year with commas: Saturday, Sep. 4, 2017.
- Omit the comma when only using month and year: September 2017. (Also, do not abbreviate the month in this case.)
- Separate geographic references with commas: The University of Oregon is in Eugene, Lane County, Ore.
- When listing a group of related items, list each one separated by a comma, except omit the comma after the second-to-last item: My siblings’ names are Anne, John, David, Peter, Paul and Mary.
- If the sentence is ambiguous without it, use the last comma: The meal consisted of steak, green beans, and macaroni and cheese.
- Separate multiple adjectives that modify the same noun with a comma: It was a windy, blustery day.
- Do not separate adjectives that are not really multiple modifications to the noun: bright green jerseys are not the same thing as bright, green jerseys.
- Set off non-essential information in a sentence with commas: Joe, who was in a hurry, left without his books. The main sentence is Joe left without his books. The fact that he was also in a hurry is additional information. Contrast this with The student who was in a hurry left without his books. In this sentence, there are other students who were not in a hurry and did not leave without their books, so who was in a hurry is essential to explaining who left without his books. (For more information, see the section on which vs. than in Common Errors and Points of Style.)
- Use a comma to join two complete sentences with a coordinating conjunction (and, but, for, or, nor, so, yet): The Ducks had a rough season last year, so they hired a new coach.
- In short sentences, you may omit the comma: I brought the hamburgers and Joe grilled them.
- Do not use a comma if one of the sentences is incoimplete: Joe is looking for four tickets but will settle for two. (Compare Joe is looking for four tickets, but he will settle for two.)
- If a dependent clause (a part of a sentence that can’t stand on its own, so it has to be part of another sentence) precedes an independent clause (a part of a sentence that is a complete sentence on its own), then separate it with a comma: If you don’t get in line soon, you won’t get a ticket. If the dependent clause follows the independent clause, do not use a comma: You won’t get a ticket if you don’t get in line soon. (Note that you won’t get a ticket is a complete sentence, and if you don’t get in line soon is not.)
- Semicolon
- A semicolon is used to join two complete and related sentences: Jim had 1,338 rushing yards last season; Joe, in three fewer games, had 1,268.
- You can also use semicolons to join complex lists when necessary: You have these choices available: green, red or blue; square, circle or triangle; and small, medium or large.
- Colon
- Use a colon to introduce a quote or example: This is a quote or example.
- Use a colon to introduce a list: Three coaching positions will report directly to the head coach: offensive coordinator, defensive coordinator and special teams coordinator.
- Use a colon to introduce a clarifying word or phrase at the end of a sentence: One thing more than any other is the source of improvement: showing up for practice.
- A colon should always follow an independent clause (a group of words that can stand on their own as a complete sentence).
- There are three open positions: left guard, center and free safety. (This is correct.)
- The three open positions are: left guard, center and free safety. (This is not correct, because The three open positions are is not a complete sentence. In this case, you could simply omit the colon: The three open positions are left guard, center and free safety.
- If a colon introduces more than one complete sentence, capitalize the word just after the colon: Willie Taggart has three rules: Make no excuses. Blame no one. Do something. If there is only one complete sentence, don’t capitalize it: Willie Taggart has one rule: do something.
- Colons are also used between the hours, minutes and seconds of a time: 3:35:07 p.m. None of the above rules apply in this situation.
Hyphen
The rules for the use of hyphens are fluid. They change fairly often, and there are many inconsistencies in the way that they are applied. For example, e-mail was common 20 years ago, and now it is almost entirely replaced with email. Online was on-line before the internet became ubiquitous. At the same time, many other less common “e” compounds still retain the hyphen, such as e-book and e-learning. For some common hyphenation errors, see Common Errors and Points of Style. For an exhaustive list of rules, see The Chicago Manual of Style’s 10-page online document on the subject. These rules also apply to AP Style.
- Dashes give a sense of interrupting the flow of thought. Use a dash to set off part of a sentence more strongly that you would by using commas: Joe gained just 43 yards — a far cry from his usual total.
- In AP style, dashes are always em dashes (—). They are always preceded and followed by a space, except when they begin or end a sentence. At the beginning of a sentence, omit the leading space; at the end of a sentence, omit the trailing space.
- An ellipsis is written as space, three periods, space, so: “ … ” with two exceptions. If the ellipsis begins the first sentence in a paragraph, omit the first space; if it ends the last sentence, omit the last space. (These exceptions don’t come up very often.)
-
- Use an ellipsis to show parts of the middle of a quotation that you are leaving out. Suppose Joe says this: “We are fumbling too often. We have to be more careful to hold on to the ball. If we don’t, we’re going to have trouble winning games. Taking care of the ball wins games.” You can use an ellipsis to abbreviate this to Joe said “We have to be more careful to hold on to the ball … Taking care of the ball wins games.” Notice that you do not put an ellipsis at the beginning of the quote, even though you don’t start it at the beginning.
- Use an ellipsis to suggest a trailing off of thought: Perhaps we could take … well, perhaps not.
- Parentheses
- Parentheses (the plural of parenthesis) are always used in pairs. Use parentheses to set off “as an aside” statements from the main flow of the sentence: Oregon has won the conference championship six times (although they have yet to win a national championship).
- The closing parenthesis goes before the final punctuation (period, question mark or exclamation point) if it is part of a larger sentence (this is an example). If it is part of an entire sentence, the final punctuation goes inside the closing parenthesis. (This is an example.)
- Quotation Marks
- Periods and commas always go inside quotation marks: Joe said “I’ll be leaving now.” and “I’ll be leaving now,” Joe said.
- All other punctuation goes outside quotation marks: Did Jim say “I know”?
- The exception to this is if the punctuation is part of the quotation itself: “Who knows?” Jane asked. “Look out!” Joe cried.
- Angle Brackets
- Use angle brackets to add your own words to a quote, to make clarifications or comments to it. Suppose Joe says this: “We are fumbling too often. We have to be more careful to hold on to the ball. If we don’t, we’re going to have trouble winning games.” You could quote Joe this way: “We are fumbling too often … If we don’t [hold on to the ball], we’re going to have trouble winning games.”
- Use [sic] to show the reader that you are quoting something as written, when there are errors in the writing. Do not correct the original quote: According to Dan Dropout of Second-Rate Sports, “Oregon has to do better with there [sic] defensive line.”
Titles of Articles
- Most words in titles are capitalized.
- Always capitalize the first and last words of a title.
- Capitalize everything else except a, an, and, at, but, by, for, in, nor, of, on, or, out, so, the, to, up, and yet. Optional exceptions:
- This one gets tricky, so feel free not to use it. So, up and yet can also be used as adverbs. When they are, you may capitalize them if you think it looks better. Examples:
- Ducks Gain Only 75 Yards Rushing yet Still Win
- Ducks Have Yet to Lose
- Jones Misses Practice so He Can Rest Injured Pinky
- Why There Are So Many Injuries in Football
- Jones Walks up the Stairs
- Jones Looks Up an Old Friend
- This one gets tricky, so feel free not to use it. So, up and yet can also be used as adverbs. When they are, you may capitalize them if you think it looks better. Examples:
-
- The way to tell if the word is being used as an adverb is to see if it modifies a verb, an adjective or another adverb. In sentence ii above, the word yet modifies the verb have. In sentence iv, the word so modifies the adjective many. In sentence vi, the word up modifies the verb looks.
- To also functions as an adverb in a few idiomatic expressions: come to (regain consciousness), fall to (start in on something, as in We fell to with a will), pull to (close, as in pull a door to). In these cases, you may also capitalize to.
- Capitalize to in the expression to and fro if you think that The Ducks Ran to and Fro looks funny.
- In particular, be careful to capitalize forms of the verb to be: am, are, is, was and were, and forms of the verb to do: do, does and did.
- If you have a title and you’re unsure whether you’re capitalizing it correctly, https://capitalizemytitle.com/ is an excellent resource to check it.
AP Style Miscellanies
- In 2010, the AP style changed from Web site to website.
- In 2011, e-mail became email. However, other e-terms still retain the hyphen: e-book, e-commerce, e-business.
- In 2014, AP style determined that more than and over could be used interchangeably, when referring to an amount: over 200 yards rushing, more than 200 yards rushing — Mike Shor’s tweet “More than my dead body!” notwithstanding.
- In 2016, Internet became internet and Web became web.
Common Writing Errors
This document addresses a number of common mistakes that people make when writing: its vs. it’s, affect vs. effect, and so on.
This document addresses a number of common mistakes that people make when writing: its vs. it’s, affect vs. effect, and so on.
Common Writing Errors
This is a list of grammar, punctuation and spelling errors that are easy to get wrong in writing and/or editing.
-
Like vs. such as: use like for comparisons and such as for inclusions. There are no teams like the New York Yankees. Teams that spend a lot of money, such as the New York Yankees, are perennial World Series contenders.
-
A compound sentence joins two complete — often shorter — sentences into one, using a coordinating conjunction to join them. (There are seven coordinating conjunctions: and, but, or, for, nor, so and yet.) When writing a compound sentence, use a comma before the conjunction that joins the two sentences: I’m a fine writer, but I’m a controversial one as well. If you are not joining two complete sentences, omit the comma: I am a fine writer but a controversial one as well.
-
Know the difference between then and than. First this, then that. I would rather be first than second. Then deals with time sequences. Than deals with comparisons and choices.
-
Contractions and possessive nouns always have apostrophes, and possessive pronouns never do. If you remember this, you will not have trouble with your vs.you’re, their vs. they’re and especially its vs. it’s (its is probably the most often misspelled word in the English language).
These are the possessive pronouns:
-
hers
-
his
-
its
-
mine
-
ours
-
theirs
-
yours
Note that none of them have an apostrophe.
-
-
Which vs. that: Use which for non-restrictive clauses and that for restrictive clauses.
A clause is group of related words containing a verb. A complete sentence, then, always has at least one clause. It will often have more than one. The words in a clause are not necessarily contiguous in a sentence.
Here is a sentence with two clauses:
Chairs that don’t have cushions are uncomfortable.
The main clause (the complete sentence) is Chairs are uncomfortable. The additional clause in the first sentence is that don’t have cushions. We don’t want to say that all chairs are uncomfortable, so we add another clause to explain that only chairs without cushions are uncomfortable. In other words, we restrict the chairs we’re calling uncomfortable to those without cushions. So, that don’t have cushions is a restrictive clause.
Now, let’s change the sentence, replacing that with which:
Chairs, which don’t have cushions, are uncomfortable.
Now, we’re not only saying that all chairs are uncomfortable, we’re saying that none of them have cushions, either! We are not restricting the type of chairs we’re talking about. So, which don’t have cushions is a non-restrictive clause.
-
Notice also that which don’t have cushions is set off by commas, and that don’t have cushions is not. So, there is one more part to the rule: non-restrictive clauses are set off by commas, and restrictive clauses aren’t.
Now that you know the difference between a restrictive and a non-restrictive clause, here is the whole rule:
Use which with non-restrictive clauses, and set off the clause with commas. Use that with restrictive clauses, and do not set off the clause with commas.
-
When using a or an with an initialism, use whichever you would use with the first letter alone: an HTML, an LCD, a CICS, an RGB, a UPS.
Initialisms are often incorrectly called acronyms. If you pronounce the abbreviation as a word, it’s an acronym: ANSI, WYSIWYG, DOS, LAMP. If you don’t, it’s an initialism: API, PHP, SMTP.. This rule does not apply to acronyms.
This can get a bit confusing. For example, it’s an SMTP server, but a SOAP message. The fact that the S in SMTP stands for simple has nothing to do with whether you use a or an in front of it. What matters is what you would use in front of the first letter S by itself.
-
Avoid misplaced modifiers. For example, they nearly scored every time they had the ball has a very different meaning from they scored nearly every time they had the ball. Misplaced modifiers (nearly is the modifier in this example) don’t always change the meaning of the sentence this drastically, but they usually make the sentence harder to understand. For example, Joe read only 10 pages is preferable to Joe only read 10 pages, because the latter could be taken to mean that Joe did nothing but read 10 pages.
As a rule, a modifier should immediately precede the word it modifies.
-
Be careful with hyphenation rules for compound adjectives. A compound adjective is a combination of two or more words that function as a single adjective. The basic reason that we hyphenate them is to clarify meaning: 300-odd editors are (probably) not the same thing as 300 odd editors (credit to GrammarMonkeys for that one).
Hyphenation rules for compound adjectives include:
-
Hyphenate compound adjectives that precede the noun they modify: off-campus apartment, run-oriented offense, first-place finish.
Note also that removing the hyphen from a compound adjective can create an alternate meaning: a small-state senator is senator in a state with a small population, while a small state senator is a state senator who is small. (Once you remove the hyphen, the two words are no longer a compound adjective, but two separate adjectives modifying the same noun.)
-
Do not hyphenate compound adjectives that follow the noun they modify: he had an apartment off campus, the offense was run oriented, they finished in first place.
-
Do not hyphenate a compound adjective that begins with an adverb ending in -ly: a carefully constructed play rather than a carefully-constructed play. (Technically, this isn’t really a compound adjective, but an adverb modifying an adjective that modifies a noun.)
-
If the beginning word is an adverb that doesn’t end in -ly, the rules vary. For example, much-needed respite but least common multiple. And some adverbs can be hyphenated or not, depending on meaning: the most-satisfied customers are customers with the highest level of satisfaction, while the most satisifed customers is the largest number of satisfied customers.
-
-
This is only a partial list. Hyphenation is complicated, and proofreaders often have to look up exactly how to hyphenate compounds. The Chicago Manual of Style’s Hyphenation Table is a comprehensive reference. All of the rules apply equally to AP Style, except CMS rules about when to use the slightly wider n-dash (–) can be ignored, as AP Style only uses hyphens. When in doubt, look it up!
-
-
Be careful not to confuse words and phrases that sound similar. Your spell checker won’t help you with these! (For an amusing take on the limitations of the spell checker, see the satirical poem Ode to a Spell Checker.) This is a list of some commonly confused words, with some explanations of commonly mangled phrases. If you’re not sure of the difference between some of the listed words, please look them up in a dictionary. (Googling “definition [your word]” will give you a dictionary definition.)
-
Affect, effect
-
This one is particularly complicated, because each one can be either a noun or a verb, and they all have different meanings. For more information, see Affect vs. Effect and 34 Other Common Confusions.
-
-
Allusion, illusion
-
Allude, elude
-
Assure, ensure, insure
-
Bait, bate
-
In particular: bated breath, not baited breath.
-
-
Born, borne
-
Breath, breathe
-
Cache, cachet
-
Capital, capitol
-
Complement, compliment
-
Core, corps
-
A corps is a body of people. So press corps, not press core.
-
-
Counsel, council
-
Criterion, criteria
-
Criterion is the singular form of criteria.
-
- Desert, dessert
-
In particular: get one’s just deserts, not get one’s just desserts. (When desert is pronounced like dessert, it means something that is deserved.)
-
-
Discreet, discrete
-
Dew, do, due
-
In particular: make do, not make due.
-
- Fewer than, less than
-
For more information, see Less vs. Fewer.
-
- Gaff, gaffe
-
Gibe, jibe
-
Hoard, horde
-
Home, hone
-
In particular: home in on, not hone in on. Although the latter is finding its way into accepted usage, it isn’t considered correct. See Hone/Home for more information.
-
-
Its, it’s
-
Its is probably the most commonly misspelled word in English, probably at least in part because the apostrophe was dropped only about 200 years ago. For help on how to get it right, see When to Use It’s vs. Its.
-
-
-
Lay, lie
-
Lead, led
-
Loath, loathe
-
Loose, lose
-
It has recently become fairly common to incorrectly use loose in place of lose: loose weight, loose my keys, etc. Lose is a verb whose basic meaning is “no longer have.” Loose is an adjective meaning “not confined.” Where the confusion arises is that loose also has a somewhat archaic function as a verb, with the meaning of “turn loose” or “set free.” Probably the best-known example of this usage is He hath loosed the fateful lightning of his terrible swift sword, from The Battle Hymn of the Republic.
-
-
Moot, mute
-
In particular: moot point, not mute point.
-
-
Peak, peek, pique
-
In particular: pique one’s interest, not peek or peak one’s interest.
-
-
Phenomenon, phenomena
-
Phenomenon is the singular form of phenomena.
-
-
Pole, poll
-
In particular: pole position, not poll position. In horse racing, distances of 1/16th of a mile are marked with a pole. Starting nearest the pole, or at the innermost position on the track, is considered an advantage. Auto racing borrowed the term to mean the first starting position. The term has since broadened, especially in slang, to mean any advantageous position.
-
-
Principal, principle
-
Rain, reign, rein
-
In particular: free rein and take the reins, not free reign and take the reigns — a common error in our horse-deprived society.
-
-
Rational, rationale
-
Right, rite
-
In particular: rite of passage, not right of passage. This is easy to get wrong, because a “right of passage” makes logical sense in the sense of being allowed to go somewhere. But the term actually comes from the many ceremonies around passage into adulthood.
-
Sight, site
-
In particular: onsite or on-site, not onsight or on-sight, to mean “at a
particular place.”
-
-
Straight, strait
-
In particular: dire straits and strait jacket, not dire straights and straight jacket.
-
-
Than, then
-
There, their, they’re
-
To, too, two
-
Toe, tow
-
In particular: toe the line, not tow the line.
-
-
Waive, wave
-
Wet, whet
-
In particular: whet one’s appetite, not wet one’s appetite.
-
-
Who’s, whose
-
Your, you’re